﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    #region MainInfo : datová základna
	/// <summary>
	/// Kompletní balík trace dat (všechny servery, soubory, data)
	/// </summary>
	public class MainInfo : IDisposable
	{
		#region Konstrukce
		public MainInfo()
		{
			this._NextIDs = new Dictionary<Type, int>();
            this.Traces = new CollectIdx<TraceInfo>();
			this.Servers = new CollectIdx<ServerInfo>();
			this.StringHeap = new StringHeap();
            this.TraceColors = (TraceColors)Steward.GetFromCache(typeof(TraceColors));
		}
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public int ClassNr { get { return Constants.ClassNumberMain; } }
		/// <summary>
		/// GID tohoto objektu
		/// </summary>
		public GID GId { get { return new GID(ClassNr, 1); } }
        /// <summary>
        /// Souhrn všech trace souborů, které jsme našli v HomeDirectory, a dostupné readery jsou ochotné je zpracovávat.
        /// Key hodnota = plné jméno souboru.
        /// </summary>
        public CollectIdx<TraceInfo> Traces { get; private set; }
		/// <summary>
		/// Kolekce serverů
		/// </summary>
		public CollectIdx<ServerInfo> Servers { get; private set; }
		/// <summary>
		/// Skladiště Stringů. Ukládá do sebe Stringy (každý nejvýše jedenkrát) a vrací na ně Pointery (Int32).
		/// Naopak z Pointeru dokáže vrátit odpovídající String.
		/// Uspoří paměť při opakovaném výskytu shodného Stringu.
		/// </summary>
		public StringHeap StringHeap { get; private set; }
		/// <summary>
		/// Základní uzel položek grafů = Root. Nezobrazuje se, ale jeho Childs tvoří základní úroveň.
		/// Obsahuje všechny nody celého systému.
		/// </summary>
		public NodeInfo NodeRoot { get; private set; }
        /// <summary>
        /// Uzel odpovídající souborům trace. this (MainInfo) je správcem Trace souborů, zakládá node pro jejich zobrazení, a ukládá si referenci na něj sem.
        /// Jeho Key = Constants.NodeMainTrace = 01:Trace
        /// </summary>
        public NodeInfo NodeTrace { get; private set; }
        /// <summary>
        /// Aktuální objekt ITraceReader, který právě nyní načítá data z trace souboru.
        /// Odkaz na tento objekt sem umísťuje instance třídy TraceInfo, při načítání dat. Po načtení dat je zde null.
        /// </summary>
        public ITraceReader CurrentTraceReader { get; internal set; }
        /// <summary>
        /// Obsahuje aktuální Trace soubor, který se načítá
        /// </summary>
        public TraceInfo CurrentTraceInfo { get; internal set; }
		/// <summary>
		/// Obsahuje aktuální TraceType z TraceInfo, který se načítá (nebo null, když se nic nenačítá)
		/// </summary>
		public string CurrentTraceType { get { return (this.CurrentTraceInfo == null ? null : this.CurrentTraceInfo.TraceType); } }
		#endregion
        #region Otevření dokumentu TraceDocument, čtení přehledu souborů, FileWatch, event DataChanged
        /// <summary>
        /// Otevře data, která specifikuje předaný dokument
        /// </summary>
        /// <param name="document"></param>
        internal void OpenDocument(TraceDocument document)
        {
            this.Document = document;

            List<string> files = new List<string>();
            if (document != null)
                files = document.CreateCurrentFileList();

            this._FileWatchStart();

            this.LoadFiles(files);
        }
        /// <summary>
        /// Přístup k objektu TraceDocument, který definuje vlastnosti aktuálního projektu
        /// </summary>
        public TraceDocument Document { get; private set; }
        /// <summary>
        /// Metoda volaná po změně nastvení dokumentu (typicky FileWatch)
        /// </summary>
        public void DocumentChanged()
        {
            this._FileWatchStart();
        }
        #region FileWatch
        /// <summary>
        /// Nastartuje sledování změn souborů v adresáři s daty projektu.
        /// Lze volat kdykoliv.
        /// </summary>
        private void _FileWatchStart()
        {
            string watchDir = this.Document.GetWatchDirectory();
            if (watchDir == null)
            {
                if (this._FileWatch != null)
                    this._FileWatch.EnableRaisingEvents = false;
                return;
            }

            bool increaseBuffer = false;
            if (this._FileWatch == null)
            {
                this._FileWatch = new System.IO.FileSystemWatcher();
                this._FileWatch.Created += new System.IO.FileSystemEventHandler(_FileWatch_Created);
                this._FileWatch.Deleted += new System.IO.FileSystemEventHandler(_FileWatch_Deleted);
                this._FileWatch.Changed += new System.IO.FileSystemEventHandler(_FileWatch_Changed);
                this._FileWatch.Renamed += new System.IO.RenamedEventHandler(_FileWatch_Renamed);
                increaseBuffer = true;
            }

            this._FileWatch.Filter = "*.*";
            this._FileWatch.IncludeSubdirectories = true;
            this._FileWatch.NotifyFilter = System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.Size;
            this._FileWatch.Path = watchDir;
            if (increaseBuffer)
                this._FileWatch.InternalBufferSize = 4 * this._FileWatch.InternalBufferSize;
            this._FileWatch.EnableRaisingEvents = true;
        }
        private void _FileWatchStop()
        {
            if (this._FileWatch != null)
                this._FileWatch.EnableRaisingEvents = false;
        }
        // Nový soubor spadne do _FileWatch_Created a potom do _FileWatch_Changed
        // Změna obsahu souboru (změna data) spadne do _FileWatch_Changed
        // Změna názvu souboru spadne do _FileWatch_Renamed
        // Nový adresář se neprojeví
        // Nový soubor v podadresáři jde do _FileWatch_Created, pak do _FileWatch_Changed (kde Name = jméno podadresáře), a pak znovu do _FileWatch_Changed, kde Name = jméno souboru.
        // Smazání se projeví v _FileWatch_Deleted (soubor) a pak v _FileWatch_Changed (změna v adresáři).
        void _FileWatch_Created(object sender, System.IO.FileSystemEventArgs e)
        {
            // Pozor: tato metoda není příliš často úspěšná, protože event Created je vyvolán okamžitě po založení souboru do adresáře.
            // Ale soubor se do něj teprve kopíruje (tj. nelze jej číst), takže s největší pravděpodobností tento soubor nebude zpracován do trace (dojde k tiché chybě).
            // Teprve po dokončení kopírování bude vyvolán event Changed, kde se bude detekovat změna v souboru a jeho načtení.
            this.OneFileAdd(e.FullPath);
        }
        void _FileWatch_Renamed(object sender, System.IO.RenamedEventArgs e)
        {
            
        }
        void _FileWatch_Changed(object sender, System.IO.FileSystemEventArgs e)
        {
            this.OneFileCheck(e.FullPath);
        }
        void _FileWatch_Deleted(object sender, System.IO.FileSystemEventArgs e)
        {
            
        }
        private System.IO.FileSystemWatcher _FileWatch;
        #endregion
		#region event DataChanged
		/// <summary>
		/// Event, který datový zdroj vyvolá tehdy, když se změní data (například vlivem nastavení) a datový zdroj sám aktivně žádá o nějakou akci do grafické vrstvy.
		/// Typicky: přenačtení dat, změny rozsahu, a jiné změny.
		/// </summary>
		internal event DataSourceEventHandler DataChanged
		{
			add { _DataChanged += value; }
			remove { _DataChanged -= value; }
		}
		private event DataSourceEventHandler _DataChanged;
		/// <summary>
		/// Invoker eventu DataChanged
		/// </summary>
		/// <param name="e"></param>
		internal virtual void OnDataChanged(DataSourceEventArgs e)
		{
			if (this._DataChanged != null)
				this._DataChanged(this, e);
		}
        /// <summary>
        /// Zavolá vizuální vrstvu a požádá ji o překreslení daného řádku.
        /// Může ji požádat o přenačtení všech elementů z datového zdroje.
        /// </summary>
        /// <param name="nodeGId">GID vizuálního řádku</param>
        /// <param name="reloadElements">Požadavek, aby si vizuální vrstva znovu načetla všechny elementy. 
        /// Bez tohoto požadavku (false) dojde jen k překreslení elementů.
        /// Díky tomu, že elementy mají kořeny v datové vrstvě (stále v sobě nesou referenci na IDataElement), promítnou se změny v datech do vizuálních elementů.
        /// Pokud bude zadáno true, dojde i k načtení nových prvků anebo k odstranění smazaných prvků. Seznam prvků je v objektu NodeInfo, který je základnou vizuálního řádku.
        /// </param>
        public void CallVisualDataChangedReDrawRow(GID nodeGId, bool reloadElements)
        {
            DataSourceEventArgs args = DataSourceEventArgs.CreateReDrawRow(nodeGId, reloadElements);
            this.OnDataChanged(args);
        }
		#endregion
		#endregion
		#region Vyhledání a přidávání dat, která jsou dostupná v main
		/// <summary>
        /// Metoda najde server podle jeho názvu. Pokud nenajde, založí nový. 
        /// Pokud je zadán prázdný název, server se nehledá ani nezakládá.
		/// </summary>
		/// <param name="serverName">Jméno serveru. Interně jsme Case-Insensitive.</param>
		/// <returns></returns>
        internal ServerInfo FindAddServer(string serverName)
		{
            if (String.IsNullOrEmpty(serverName)) return null;

			// Zkusím najít ID serveru podle názvu:
			ServerInfo server;
            if (this.Servers.TryGetValue(serverName, out server))
				return server;

			server = new ServerInfo(this, serverName);
			this.Servers.Add(server);
            server.LoadFromDocument();

			return server;
		}
        /// <summary>
        /// Metoda přidá nový trace soubor.
        /// Trace soubor má být načten (hlavička) a připraven metodou this.DetectAndAddTrace().
        /// Trace soubor se přidává do Main (kde se plně eviduje), a současně se přidává do nodu NodeTrace jako prvek Elements (aby za sebe generoval vizuální element).
        /// </summary>
        /// <param name="trace">Data o souboru trace</param>
        internal void AddTrace(TraceInfo trace)
        {
            if (trace == null || String.IsNullOrEmpty(trace.Key)) return;

            // Přidat do kolekce (indexovaná ID i Key):
            this.Traces.Add(trace);

            // Do řádku NodeTrace, kde figuruje jako element (protože TraceInfo implementuje IElementCreator):
            this.NodeTrace.AddElement((IDataElement)trace);
        }
        /// <summary>
        /// Do řádku NodeTrace zapíše všechny trace soubory.
        /// </summary>
        internal void AddTracesIntoNode()
        {
            this.NodeTrace.ClearElements();
            foreach (TraceInfo trace in this.Traces)
                this.NodeTrace.AddElement((IDataElement)trace);
        }
        /// <summary>
        /// Pokusí se najít a vrátit TraceInfo
        /// </summary>
        /// <param name="traceGId"></param>
        /// <returns></returns>
        internal TraceInfo FindTrace(GID traceGId)
        {
            TraceInfo traceInfo;
            if (this.Traces.TryGetValue(traceGId.RecordNumber, out traceInfo))
                return traceInfo;
            return null;
        }
        #endregion
		#region Číslování položek, správa barev a další datový servis
		/// <summary>
		/// Vrátí číslo ID pro položku předaného typu.
		/// Není zcela threadové bezpečné, ale v cílové oblasti používání nehrozí multithreading.
		/// </summary>
		/// <param name="applicant"></param>
		/// <returns></returns>
		internal int NextID(object applicant)
		{
			if (applicant == null) return 0;
			Type type = applicant.GetType();
			if (!this._NextIDs.ContainsKey(type))
			{
				lock (this._NextIDs)
				{
					if (!this._NextIDs.ContainsKey(type))
						this._NextIDs.Add(type, 0);
				}
			}
			int result = ++this._NextIDs[type];
			return result;
		}
        /// <summary>
        /// Metoda vrátí barvu pro element dané skupiny (název odpovídá konfiguračnímu XML), 
        /// určitého čísla ID (aby jeden objekt měl stále tutéž barvu, ale vedlejší objekty téže skupiny měly barvu mírně odlišnou).
        /// </summary>
        /// <param name="colorGroup"></param>
        /// <param name="objectId"></param>
        /// <returns></returns>
        public Color GetColor(string colorGroup, int objectId)
        {
            return this.TraceColors.GetColor(colorGroup, objectId);
        }
        private Dictionary<Type, int> _NextIDs;
        /// <summary>
        /// Správce barev
        /// </summary>
        public TraceColors TraceColors { get; private set;}
		#endregion
		#region Načítání přehledu trace souborů, detekce serverů, detekce časů souborů atd. Nenačítá se Content souborů.
		/// <summary>
		/// Načte obsah adresáře a podadresářů, sestaví soupis serverů (oddělené adresáře a odlišné názvy souborů), 
		/// do každého serveru vloží data o všech jeho souborech, z nich načte jejich časový interval, a soubory setřídí podle počátku času.
		/// </summary>
		/// <param name="dir"></param>
		internal void LoadDir(string dir)
		{
            // Zmapování fyzických souborů:
            List<string> files = new List<string>(System.IO.Directory.GetFiles(dir, "*.*", System.IO.SearchOption.AllDirectories));
            files.Sort();

            // Načtu soubory a jejich základní vlastnosti:
            this.LoadFiles(files);
		}
        /// <summary>
        /// Načte obsah daných souborů, sestaví soupis serverů (oddělené adresáře a odlišné názvy souborů), 
        /// do každého serveru vloží data o všech jeho souborech, z nich načte jejich časový interval, a soubory setřídí podle počátku času.
        /// </summary>
        /// <param name="dir"></param>
        internal void LoadFiles(List<string> files)
        {
            // Status bar:
            int pgbPos = 0;
            int pgbCnt = files.Count;
            Steward.StatbusBarSet(StatusBarItem.Progress, 0M);

            // Připravím si Tree a základní nody:
            this._TreePrepare();

            // Zmapování fyzických souborů:
            this.Document.Auditlog.Clear();                   // Hlášení problémů necháme na uživateli. V toolbaru se mu rozsvítí auditlog, pokud existuje nějaký problém.
            if (files != null)
            {
                foreach (string file in files)
                {   // Detekce a zpracování souboru je velice rychlé, řádově 0.5 milisekundy na soubor ! :
                    pgbPos++;
                    Steward.StatbusBarSet(StatusBarItem.Progress, (decimal)pgbPos / (decimal)pgbCnt);

                    this.DetectAndAddTrace(new System.IO.FileInfo(file));
                }
            }

            // Finalizace datumů:
            this.LoadDirFinalize();

            Steward.StatbusBarSetReady();

            // Hlášení problémů necháme na uživateli. V toolbaru se mu rozsvítí auditlog, pokud existuje nějaký problém.
            // this.Document.Auditlog.Show("Problém", false);
        }
        /// <summary>
        /// Interaktivně doplní do this.Traces fyzický soubor, pokud je nový (tj. dosud jej nemáme načtený)
        /// </summary>
        /// <param name="fileName"></param>
        internal void OneFileCheck(string fileName)
        {
            // Prověříme vstup:
            if (String.IsNullOrEmpty(fileName)) return;
            if (!System.IO.File.Exists(fileName)) return;

            // Zjistíme, zda soubor máme načten:
            System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
            TraceInfo traceInfo = this.Traces.FirstOrDefault(t => String.Equals(t.FileInfo.FullName, fileInfo.FullName, StringComparison.OrdinalIgnoreCase));

            if (traceInfo != null)
                return;

            this.OneFileAdd(fileName);
        }
        /// <summary>
        /// Interaktivně doplní do this.Traces nový soubor
        /// </summary>
        /// <param name="fileName"></param>
        internal void OneFileAdd(string fileName)
        {
            TraceInfo trace = this.DetectAndAddTrace(new System.IO.FileInfo(fileName));
            if (trace == null) return;                  // Přidávaný soubor nikoho nezajímá, nikdo jej nezpracoval.

            // Finalizace datumů:
            this.LoadDirFinalize();

            // Invalidace dat grafu pro časový interval načteného souboru:
            TimeRange traceTime = trace.Time;
            this.OnDataChanged(DataSourceEventArgs.CreateInvalidateTimeRange(traceTime));
        }
        /// <summary>
        /// Určí druh zadaného souboru, určí (najde / vytvoří) server, vytvoří TraceInfo a vloží jej do serveru.
        /// Data nejsou načtena, hlavička souboru je detekována.
        /// Výstup: objekt TraceInfo (soubor byl rozpoznán a uložen do this.Traces)  /   null (nikdo to nechce dělat).
        /// Tato metoda odchytává a audituje chyby.
        /// </summary>
        /// <param name="main"></param>
        /// <param name="fileInfo"></param>
        protected TraceInfo DetectAndAddTrace(System.IO.FileInfo fileInfo)
        {
            TraceInfo result = null;
            try
            {
                string file = System.IO.Path.Combine(fileInfo.Directory.Name, fileInfo.Name);
                Steward.StatbusBarSet(Forms.StatusBarItem.MainText, "Detecting file: " + file);

                result = this.DetectAndAddTraceTry(fileInfo);
            }
            catch (Exception exc)
            {
                this.Document.Auditlog.AddAuditError(exc, new MasterDetailItem("FileName", fileInfo.FullName, new Size(240, 0))); 
            }
            return result;
        }
        /// <summary>
        /// Určí druh zadaného souboru, určí (najde / vytvoří) server, vytvoří TraceInfo a vloží jej do serveru.
        /// Data nejsou načtena, hlavička souboru je detekována.
        /// Výstup: objekt TraceInfo (soubor byl rozpoznán a uložen do this.Traces)  /   null (nikdo to nechce dělat).
        /// Tato metoda může generovat chyby (neodchytává je).
        /// Tato metoda může mít na vstupu ZIP soubor
        /// </summary>
        /// <param name="main"></param>
        /// <param name="fileInfo"></param>
        protected TraceInfo DetectAndAddTraceTry(System.IO.FileInfo fileInfo)
        {
            if (fileInfo == null || !fileInfo.Exists)
                return null;

            // Soubory s příponou HEGTV nebo HEGTA nebudeme načítat:
            if (String.Equals(fileInfo.Extension, ".hegtv", StringComparison.InvariantCultureIgnoreCase) || String.Equals(fileInfo.Extension, ".hegta", StringComparison.InvariantCultureIgnoreCase))
                return null;

            // Získám přehled objektů, které jsou ochotny načítat vstupní soubory:
            //   (Toto je místo, kde se ve Stewardu načtou všechny objekty, které implementují interface ITraceReader, a vytvoří se z nich instance, která se cachuje)
            IEnumerable<ITraceReader> traceReaders = Support.Steward.GetServiceObjectList<ITraceReader>(true);
            List<KeyValuePair<ITraceReader, TraceReaderCheckNameArgs>> selectedReaders = new List<KeyValuePair<ITraceReader, TraceReaderCheckNameArgs>>();   // Ty readery, které se chtějí podívat dovnitř
            foreach (ITraceReader traceReader in traceReaders)
            {
                if (traceReader == null) continue;
                // Každého readeru se zeptám, jestli by tento soubor chtěl vidět:
                TraceReaderCheckNameArgs nameArgs = new TraceReaderCheckNameArgs(this, fileInfo);
                traceReader.CheckFileName(nameArgs);
                if (nameArgs.CheckContent)
                    selectedReaders.Add(new KeyValuePair<ITraceReader, TraceReaderCheckNameArgs>(traceReader, nameArgs));
            }

            // Pokud se k danému souboru nikdo nehlásí, skončím:
            if (selectedReaders.Count == 0)
            {
                this.Document.Auditlog.AddAuditItem(fileInfo.Name, MasterDetailItem.LevelWarning,
                    new MasterDetailItem("Message", "K souboru se nepřihlásil žádný z readerů.", new Size(240, 0)),
                    new MasterDetailItem("Filename", fileInfo.FullName, new Size(240, 0)));
                return null;
            }

            // Někdo by se rád podíval dovnitř => tak otevřeme soubor:
            decimal winPriority = 0M;
            ITraceReader winReader = null;
            TraceReaderCheckNameArgs winNameArgs = null;
            TraceReaderCheckContentArgs winContentArgs = null;

            using (System.IO.FileStream fs = new System.IO.FileStream(fileInfo.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            using (System.IO.StreamReader sr = new System.IO.StreamReader(fs, true))
            // using (System.IO.StreamReader fsr = new System.IO.StreamReader(fileInfo.FullName))       // nelze řídit FileAccess
            {
                foreach (KeyValuePair<ITraceReader, TraceReaderCheckNameArgs> readArgsKvp in selectedReaders)
                {
                    // Připravím soubor na začátek:
                    sr.BaseStream.Position = 0L;
                    sr.DiscardBufferedData();

                    // Připravím argument Content:
                    TraceReaderCheckContentArgs contentArgs = new TraceReaderCheckContentArgs(this, fileInfo, sr);
                    ITraceReader traceReader = readArgsKvp.Key;
                    contentArgs.TraceDate = readArgsKvp.Value.TraceDate;  // Pokud si reader odvodil datum souboru v první metodě (traceReader.CheckFileName), převezmeme datum do nového argumentu (odkud jej předáváme do TraceInfo).
					contentArgs.TraceType = readArgsKvp.Value.TraceType;                     //   dtto pro typ trace souboru
                    contentArgs.ServerTimePriority = readArgsKvp.Value.ServerTimePriority;   //  ... a pro časovou prioritu.
                    contentArgs.UserData = readArgsKvp.Value.UserData;                       //   dtto pro všechna další userdata
                    traceReader.CheckContent(contentArgs);

                    // Pokud je to zatím nejlepší reader, zapamatuji si ho:
                    if (contentArgs.Priority > winPriority)
                    {
                        winPriority = contentArgs.Priority;
                        winReader = traceReader;
                        winNameArgs = readArgsKvp.Value;
                        winContentArgs = contentArgs;
                    }
                }
                sr.Close();
            }
            
            // Pokud nikdo nevyhrál (priorita == 0M), skončím, soubor se tím pádem nikam nezařadí a bude se ignorovat:
            if (winPriority == 0M)
            {
                
                this.Document.Auditlog.AddAuditItem(fileInfo.Name, MasterDetailItem.LevelWarning,
                    new MasterDetailItem("Message", "O soubor nemá zájem žádný z readerů.", new Size(240, 0)),
                    new MasterDetailItem("Filename", fileInfo.FullName, new Size(240, 0)));
                return null;
            }

            // Máme výherce a tím máme i data v argumentech:

            // Najdeme / založíme server?
            ServerInfo server = this.FindAddServer(winNameArgs.ServerName);
            if (server == null)
            {
                this.Document.Auditlog.AddAuditItem(fileInfo.Name, MasterDetailItem.LevelWarning,
                    new MasterDetailItem("Message", "Reader neurčil jméno serveru, do něhož se data mají načítat.", new Size(240, 0)),
                    new MasterDetailItem("Filename", fileInfo.FullName, new Size(240, 0)));
                return null;
            }

            // Vyřeším prioritu času:
            server.ServerTimePriority = winContentArgs.ServerTimePriority;

            // Zajistíme, že v hlavním nodu budou připraveny subnody, které deklaruje reader:
            this.RootNodeAddPaths(winContentArgs.TreePaths, winContentArgs.TraceType);

            // Založíme nový objekt pro tento trace soubor:
            TraceInfo trace = new TraceInfo(server, new System.IO.FileInfo(fileInfo.FullName), winReader.GetType(), winContentArgs);

            // Trace soubor přidám do evidence v Main a v Server:
            this.AddTrace(trace);
            server.AddTrace(trace, winContentArgs.TraceType);

            return trace;
        }
        /// <summary>
        /// Provede závěrečné výpočty po načtení trace souborů.
        /// Určí sumární časy Main, Servers.
        /// Určí pořadí Server v grafu (ElementGroup)
        /// </summary>
        protected void LoadDirFinalize()
        {
			List<TraceInfo> traceList = this.Traces.List;

            // Setřídění serverů podle jejich názvu, tím určím číslo skupiny ElementGroup pro soubory trace = vizuální elementy, v oddělených grupách:
            List<ServerInfo> servers = this.Servers.SortedList;
			List<double> avgTimes = new List<double>();
            int serverCnt = servers.Count;
			TimeRange mainTime = TimeRange.Empty;
			for (int s = 0; s < serverCnt; s++)
            {
                servers[s].ElementGroup = (serverCnt - s);

				TimeRange serverTime = TimeRange.Empty;
				List<TraceInfo> traceServerList = traceList.FindAll(t => t.Server.Id == servers[s].Id);  // Vyberu trace soubory tohoto serveru
				traceServerList.Sort((a, b) => a.Time.Begin.CompareTo(b.Time.Begin));                    // Setřídím je datumem
				for (int t = 0; t < traceServerList.Count; t++)
				{	// Do prvků dle setříděného seznamu trace jednoho serveru vepíšu jejich TraceOrder, a posčítám jejich Time:
					traceServerList[t].TraceOrder = t;
					TimeRange traceTime = traceServerList[t].Time;
					avgTimes.Add(traceTime.Seconds);       // Statistika průměrného času
					serverTime += traceTime;
				}
				mainTime += serverTime;                    // Součet času Main.Time
				servers[s].Time = serverTime;
            }
			this.Time = mainTime;

            // Průměrný čas:
            double avgSec = 0D;
            int avgCnt = avgTimes.Count;
            if (avgCnt > 0)
            {
                if (avgCnt > 4)
                {
                    // Statistika jako ve sportu: odeberu nejmenší a největší čas, a průměr bude z těch středních hodnot:
                    avgTimes.Sort();
                    avgTimes.RemoveAt(0);
                    avgTimes.RemoveAt(avgCnt - 2);
                    avgCnt -= 2;
                }
                avgSec = avgTimes.Sum() / (double)avgCnt;
            }
            this.TimeAverage = TimeSpan.FromSeconds(avgSec);
        }
		/// <summary>
		/// Obsahuje sumární načtený časový úsek ze všech serverů
		/// </summary>
		public TimeRange Time { get; internal set; }
		/// <summary>
		/// Obsahuje průměrný čas jednoho souboru
		/// </summary>
		public TimeSpan TimeAverage { get; internal set; }
        /// <summary>
        /// Vrátí časový interval, který by měl být zobrazený po inicializaci.
        /// Je to čas, který má délku průměrného trace souboru, a končí na konci posledního souboru.
        /// </summary>
        /// <returns></returns>
        internal TimeRange TimeInitial
        {
            get
            {
                TimeRange totalTime = this.Time;
                TimeSpan avgTime = this.TimeAverage;
                if (!totalTime.IsEmpty && avgTime.Ticks > 0L)
                    return new TimeRange(totalTime.End - avgTime, totalTime.End);
                return new TimeRange(DateTime.Now.AddDays(-1), 86400D);
            }
        }
		#endregion
        #region Načítání obsahu trace souborů (content) pro požadovaný časový interval. Zpracování jednotlivých načítaných položek ITraceItem.
        /// <summary>
        /// Zajistí načtení dat z trace souborů pro požadovaný časový úsek.
        /// Načítá jen ty soubory, které dosud načteny nejsou.
        /// </summary>
        /// <param name="timeRange">Čas, pro který se má zajistit načtení dat</param>
		/// <param name="traceType">Druh trace souborů, které je třeba načíst.
		/// Je ukládán v nodech na základě dat, které reader deklaroval při načítání obsahu souboru.</param>
        public void CheckLoadContentForTime(TimeRange timeRange, string traceType)
        {
            List<TraceInfo> traces = this.GetTracesForTime(timeRange, true, traceType);
			if (traces.Count > 0)
			{
                // Progres ve status baru: za 1 soubor = 5 jednotek, na finalizaci 1 jednotka za každý soubor (naskočí najednou):
                decimal progresTotal = 6M * (decimal)traces.Count;
                decimal progresCurrent = 0M;

                int processedFiles = 0;
                foreach (TraceInfo trace in traces)
                {
                    progresCurrent += 5M;
                    Steward.StatbusBarSet(Forms.StatusBarItem.Progress, (decimal)(progresCurrent / progresTotal));

                    if (this.TraceCheckProcessed(trace, traceType, null))
                        processedFiles++;
                }

				// Po změnách provést finalizaci všech jednotek:
                Steward.StatbusBarSet(Forms.StatusBarItem.Progress, (decimal)(1M));
                if (processedFiles > 0)
                {
                    Steward.StatbusBarSet(Forms.StatusBarItem.MainText, "Preparing data...");
                    this.LoadContentFinalize();
                }

                Steward.StatbusBarSetReady();
			}
        }
        /// <summary>
        /// Zajistí načtení jednoho trace souboru.
        /// Vypíše info o načítání do Status baru, načte soubor, vyčistí StatusBar (volitelně), vyčistí paměť a vypíše ji do StatusBaru (vždy).
        /// </summary>
        /// <param name="trace">Soubor Trace k načtení</param>
        /// <param name="statusText">Text, který se má vložit do StatusBaru po načtení souboru. null = nic. Běžně se vkládá text Ready.</param>
        internal bool TraceCheckProcessed(TraceInfo trace, string traceType, string statusText)
        {
            bool needProcess = trace.NeedProcessForTraceType(traceType);

            if (needProcess)
            {
                Steward.StatbusBarSet(Forms.StatusBarItem.MainText, "Loading file: " + trace.FileDirName);

                needProcess = trace.CheckProcessed(traceType);

                // Odebrat text ze StatusBaru:
                if (statusText != null)
                    Steward.StatbusBarSet(Forms.StatusBarItem.MainText, statusText);

                // Vyčistit paměť a zobrazit její stav:
                Steward.MemoryGcFinish();
            }
            return needProcess;
        }
        /// <summary>
        /// Zobrazí v dialogovém okně hlášku o počtu zákaldních objektů v paměti
        /// </summary>
        public void ShowMemoryInfo()
        {
            int fileProcessedCnt = this.TraceProcessedCount;
            int nodeCnt = this.NodeRoot.NodeCount;
            int elementCnt = this.NodeRoot.NodeElementsCount;
            int processCnt = this.ProcessCount;
            int eventCnt = this.EventCount;

            string msg =
                "Počet zpracovaných souborů (TraceInfo): " + fileProcessedCnt.ToString() + Environment.NewLine +
                "Počet Node (NodeInfo): " + nodeCnt.ToString() + Environment.NewLine +
                "Počet Element (IDataElement) celkem: " + elementCnt.ToString() + Environment.NewLine +
                "Počet Process (ProcessInfo) celkem: " + processCnt.ToString() + Environment.NewLine +
                "Počet Event (EventInfo) celkem: " + eventCnt.ToString();
            System.Windows.Forms.MessageBox.Show(msg, "Statistika", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
        }
        /// <summary>
        /// Metoda najde GID trace souboru, který leží nejblíže danému TimeVectoru a dosud není připraven (načten jeho obsah).
        /// Využívá se při tvorbě úkolů pro volný čas: tento trace soubor bude načten, když se zrovna nic jiného nenačítá.
        /// </summary>
        /// <param name="vector"></param>
        /// <returns></returns>
        internal GID FindFirstTraceToPrepare(TimeVector vector)
        {
            List<TraceInfo> traces = this.GetTracesForTime(TimeRange.Empty, true);
            int tracesCount = traces.Count;
            if (tracesCount == 0) return GID.Empty;
            if (tracesCount == 1) return traces[0].GId;

            // Pokud máme více trace souborů, a je zadán prázdný vector, pak si vytvoříme vector odpovídající konci celého času:
            if (vector.IsEmpty)
                vector = new TimeVector(this.Time.End, TimeRange.TimeDirection.ToHistory);

            // Máme více nenačtených souborů. Který z nich to bude?
            List<KeyValuePair<double, TraceInfo>> timeTraces = new List<KeyValuePair<double,TraceInfo>>();
            DateTime vectorDate = vector.Date;
            TimeRange.TimeDirection vectorDir = vector.Direction;

            // Určím distanci timerange od vektoru:
            foreach (TraceInfo trace in traces)
            {
                double distance = 0D;           // Určím vzdálenost [sekund] od bodu TimeVectoru: kladná ve správném směru, záporná v opačném:
                switch (vectorDir)
                {
                    case TimeRange.TimeDirection.ToFuture:
                        if (trace.Time.Begin <= vectorDate && trace.Time.End > vectorDate)       // Tento čas je "přes (over)" vektor
                            distance = 0D;
                        else if (trace.Time.Begin > vectorDate)                                  // Tento čas je za vektorem
                            distance = ((TimeSpan)(trace.Time.Begin - vectorDate)).TotalSeconds;
                        else                                                                     // Tento čas je před vektorem, na opačné straně než potřebujeme
                            distance = ((TimeSpan)(trace.Time.End - vectorDate)).TotalSeconds;
                        break;

                    case TimeRange.TimeDirection.ToHistory:
                        if (trace.Time.End >= vectorDate && trace.Time.Begin < vectorDate)       // Tento čas je "přes (over)" vektor
                            distance = 0D;
                        else if (trace.Time.End < vectorDate)                                    // Tento čas je před vektorem
                            distance = ((TimeSpan)(vectorDate - trace.Time.End)).TotalSeconds;
                        else                                                                     // Tento čas je za vektorem, na opačné straně než potřebujeme
                            distance = ((TimeSpan)(vectorDate - trace.Time.Begin)).TotalSeconds;
                        break;

                }
                timeTraces.Add(new KeyValuePair<double,TraceInfo>(distance, trace));
            }

            // Setřídím: nejprve ty ve vhodné pozici, nejdříve ty nejbližší, pak ty na opačné straně, nakonec ty vzdálené:
            timeTraces.Sort(delegate(KeyValuePair<double, TraceInfo> a, KeyValuePair<double, TraceInfo> b)
            {
                if (a.Key >= 0D && b.Key < 0D) return -1;            // "a" je na správné straně, "b" na opačné
                if (b.Key >= 0D && a.Key < 0D) return 1;             // "b" je na správné straně, "a" na opačné

                return (Math.Abs(a.Key).CompareTo(Math.Abs(b.Key)));  // Nižší čísla dopředu, bez ohledu na znaménko
            });

            return timeTraces[0].Value.GId;
        }
        /// <summary>
        /// Metoda načte obsah jednoho konkrétního trace souboru
        /// </summary>
        /// <param name="traceFileGId"></param>
        internal void LoadContentForTraceFile(GID traceFileGId)
        {
            TraceInfo traceInfo = this.FindTrace(traceFileGId);
            if (traceInfo == null) return;
            if (traceInfo.ContentProcessed) return;

            traceInfo.CheckProcessed(null);

            // Po změnách provést finalizaci všech jednotek:
            this.LoadContentFinalize();
        }
        /// <summary>
		/// Vrátí všechny trace soubory, které mají záznamy pro daný čas.
		/// </summary>
		/// <param name="timeRange">Časový interval, pro který hledám trace soubory. Poud bude Empty, budou se hledat všechny soubory.</param>
        /// <param name="onlyUnProcessed">Požadavek (true), aby se do výstupu dávaly pouze soubory nezpracované (které mají trace.ContentProcessed = false) 
        /// / anebo (false) = dávat do výstupu všechny trace soubory, které vyhovují časově</param>
		/// <returns></returns>
		internal List<TraceInfo> GetTracesForTime(TimeRange timeRange, bool onlyUnProcessed)
        {
            return this.GetTracesForTime(timeRange, onlyUnProcessed, null);
        }
        /// <summary>
		/// Vrátí všechny trace soubory, které mají záznamy pro daný čas.
		/// </summary>
		/// <param name="timeRange">Časový interval, pro který hledám trace soubory. Poud bude Empty, budou se hledat všechny soubory.</param>
        /// <param name="onlyUnProcessed">Požadavek (true), aby se do výstupu dávaly pouze soubory nezpracované (které mají trace.ContentProcessed = false) 
        /// / anebo (false) = dávat do výstupu všechny trace soubory, které vyhovují časově</param>
		/// <returns></returns>
        internal List<TraceInfo> GetTracesForTime(TimeRange timeRange, bool onlyUnProcessed, string traceType)
        {
			List<TraceInfo> result = new List<TraceInfo>();
            bool allTime = timeRange.IsEmpty;
			foreach (TraceInfo trace in this.Traces)
            {
                bool add = false;
                if (allTime || trace.Time.IsConjunct(timeRange))
                {   // Pro celý čas nebo pro čas, do něhož tento trace soubor zasahuje:
                    if (onlyUnProcessed)
                        // Jen nezpracované => přidat, pokud se soubor má zpracovat pro řádky daného typu:
                        add = trace.NeedProcessForTraceType(traceType);
                    else
                        // Všechny bez ohledu na zpracování, vybíráme podle typu řádku:
                        add = trace.IsTraceType(traceType);
                }

                if (add)
                    result.Add(trace);
            }
			return result;
		}
		/// <summary>
		/// Metoda zajistí provedení dokončovacích operací po načtení obsahu trace souborů.
		/// Zajistí mergování (sloučení) událostí, které sloučit lze.
        /// Zajistí detekci časového posunu.
		/// </summary>
		internal void LoadContentFinalize()
		{
			foreach (ServerInfo serverInfo in this.Servers)
				serverInfo.LoadContentFinalize();

            foreach (TraceInfo traceInfo in this.Traces.Where(t => t.ContentProcessed))
                traceInfo.LoadContentFinalize();
		}
		#endregion
		#region Scanování trace souborů = čtení jejich obsahu a předávání do určených metod, ale bez ukládání dat
		/// <summary>
		/// 
		/// </summary>
		/// <param name="traceFilter"></param>
		/// <param name="processEdge"></param>
		/// <param name="processEvent"></param>
        public void ScanContentOfTrace(bool allTraces, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent)
        {
            this.ScanContentOfTrace(allTraces, processEdge, processEvent, null);
        }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="traceFilter"></param>
		/// <param name="processEdge"></param>
		/// <param name="processEvent"></param>
        public void ScanContentOfTrace(bool allTraces, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent, Action<ProgressData> onProgress)
		{
			Func<TraceInfo, bool> predicate = null;
			if (!allTraces)
				predicate = new Func<TraceInfo, bool>(trace => !trace.ContentProcessed);

            this.ScanContentOfTrace(predicate, processEdge, processEvent, onProgress);
		}
		/// <summary>
		/// Zajistí scanování všech trace souborů, které vyhovují daném predikátu (první parametr).
		/// Scanování = načítání položek trace souborů a jejich odesílání do specifikovaných metod.
		/// </summary>
		/// <param name="traceFilter">Filtr na trace soubory. Pokud bude null, budou se scanovat všechny.</param>
		/// <param name="processEdge">Metoda pro zpracování položek typu IEventEdge. Může být null.</param>
		/// <param name="processEvent">Metoda pro zpracování položek typu IEventItem. Může být null.</param>
        public void ScanContentOfTrace(Func<TraceInfo, bool> traceFilter, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent)
        {
            this.ScanContentOfTrace(traceFilter, processEdge, processEvent, null);
        }
		/// <summary>
		/// Zajistí scanování všech trace souborů, které vyhovují daném predikátu (první parametr).
		/// Scanování = načítání položek trace souborů a jejich odesílání do specifikovaných metod.
		/// </summary>
		/// <param name="traceFilter">Filtr na trace soubory. Pokud bude null, budou se scanovat všechny.</param>
		/// <param name="processEdge">Metoda pro zpracování položek typu IEventEdge. Může být null.</param>
		/// <param name="processEvent">Metoda pro zpracování položek typu IEventItem. Může být null.</param>
        public void ScanContentOfTrace(Func<TraceInfo, bool> traceFilter, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent, Action<ProgressData> onProgress)
		{
            // Připravit seznam trace:
			List<TraceInfo> traces = new List<TraceInfo>();
			foreach (TraceInfo trace in this.Traces)
			{
				if (traceFilter == null || traceFilter(trace))
					traces.Add(trace);
			}

            // Scan & progress:
            decimal progresTotal = 2M * (decimal)traces.Count;
            decimal progresCurrent = 1M;
            if (onProgress == null)
            {   // Progress do StatusBaru:
                foreach (TraceInfo trace in traces)
                {
                    Steward.StatbusBarSet(Forms.StatusBarItem.Progress, (decimal)(progresCurrent / progresTotal));
                    bool processed = trace.ScanContent(processEdge, processEvent);
                    if (!processed) break;         // Scan => Abort
                    progresCurrent += 2M;
                }

                // Konec status baru:
                Steward.StatbusBarSet(Forms.StatusBarItem.Progress, (decimal)(1M));
                Steward.StatbusBarSetReady();
            }
            else
            {   // Progres do handleru onProgress: 
                foreach (TraceInfo trace in traces)
                {
                    ProgressData progressData = new ProgressData()
                    {
                        ProgressCaptionVisible = true,
                        ProgressCaptionText = "Scan files",
                        ProgressBarVisible = true,
                        ProgressBarRatio = (float)(progresCurrent / progresTotal),
                        ProgressCountVisible = true,
                        ProgressCountText = System.IO.Path.GetFileName(trace.FullFileName)
                    };
                    onProgress(progressData);
                    if (progressData.CancelProcess) break;
                    
                    bool processed = trace.ScanContent(processEdge, processEvent);
                    if (!processed) break;         // Scan => Abort
                    progresCurrent += 2M;
                }
            }
		}
		#endregion
        #region Uvolňování paměti
        /// <summary>
        /// Uvolní paměť.
        /// </summary>
        internal void MemoryFree()
        {
            // Zahodí všechny elementy, shodí příznak načtení všech souborů.
            // Ponechává prázdnou konstrukci řádků, ponechává seznam trace souborů.
            this.NodeRoot.MemoryFree();

            // Smaže události ze serverů:
            foreach (ServerInfo server in this.Servers)
                server.MemoryFree();

            // Zruší příznak načtení souborů:
            foreach (TraceInfo trace in this.Traces)
                trace.DropContent();

            // Naplnit základní přehled trace souborů do nodu this.NodeTrace:
            this.AddTracesIntoNode();

            // Invalidace dat grafu pro všechny řádky:
            this.OnDataChanged(DataSourceEventArgs.CreateInvalidateElementInAllRows(true));

			// Vyprázdnit StringHeap:
			this.StringHeap.ClearMemory();

            // Vyčistit paměť a vypsat jí:
            Steward.MemoryGcFinish();
        }
        #endregion
        #region TreeNodes : příprava, výchozí naplnění, průběžné doplňování, vyhledání nodu podle jeho GIDu
        /// <summary>
		/// Připraví základní nody do RootTree
		/// </summary>
		private void _TreePrepare()
		{
            // Založím NodeRoot: ten se nekreslí, ale dělá správce pro své SubNodes. Childs na úrovni Root budou tříděné dle Key:
            this.NodeRoot = new NodeInfo(this, null, Constants.NodeMainRoot, null, RowGraphMode.NoDrawingElements, RowProperties.ChildSortedKey);

            // Do Root řádku (MainTree) založím pouze jeden řádek za trace soubory, protože ty jsou v mé režii:
            GraphRowDeclarationCls d = GraphRowDeclarationCls.StandardMultiTask(14, 0.9M, RowProperties.DynamicElementsLoading | RowProperties.PreLoadingElements);
            TreePathItem treeItem = new TreePathItem(Constants.NodeMainTrace, "Trace soubory", d);
            this.NodeTrace = this.NodeRoot.AddChild(treeItem, null);
		}
        /// <summary>
        /// Metoda zajistí, že v this.NodeRoot budou přítomny všechny zde deklarované nody.
        /// </summary>
        /// <param name="treePaths">Seznam cest k nodům</param>
        internal void RootNodeAddPaths(TreePath[] treePaths, string traceType)
        {
            if (treePaths != null)
            {
                foreach (TreePath treePath in treePaths)
                    this.NodeRootAddPath(treePath, traceType);
            }
        }
        /// <summary>
        /// Metoda zajistí, že v this.NodeRoot bude přítomna celá zde deklarovaná cesta k nodu.
        /// </summary>
        /// <param name="treePath">Cesta k nodu</param>
		internal void NodeRootAddPath(TreePath treePath, string traceType)
        {
            this.NodeRoot.AddPath(treePath, traceType);
        }
		/// <summary>
		/// Najde a vrátí node podle jeho GIDu.
		/// Pokud nenajde, vrátí null.
		/// </summary>
		/// <param name="gId"></param>
		/// <returns></returns>
		public NodeInfo FindNode(GID gId)
		{
            return this.NodeRoot.Root.FindNode(gId);
		}
		#endregion
        #region Vyhledání jednodušších dat, služby pro externí funkce
		/// <summary>
		/// Vrátí jednu konkrétní událost (IEventItem), pro daný pointer z grafické vrstvy.
		/// </summary>
		/// <param name="pointer"></param>
		/// <returns></returns>
		public IEventItem FindEvent(DataPointerStr pointer)
		{
			IDataElement element = this.FindElement(pointer);
			if (element is EventInfo)
			{
				EventInfo eventInfo = element as EventInfo;
				if (eventInfo != null)
					return eventInfo.EventItem;
			}
			return null;
		}
		/// <summary>
		/// Vrátí data jednoho elementu podle daného pointeru.
		/// </summary>
		/// <param name="pointer"></param>
		/// <returns></returns>
		public IDataElement FindElement(DataPointerStr pointer)
		{
			NodeInfo node = this.FindNode(pointer.Row);
			if (node != null)
			{
				IDataElement element = node.FindElement(pointer.Element);
				return element;
			}
			return null;
		}
		/// <summary>
        /// Vrátí souhrn událostí (IEventItem), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <returns></returns>
        public List<KeyValuePair<DataPointerStr, IEventItem>> FindEvents(IEnumerable<DataPointerStr> pointers)
        {
            return this.FindEvents(pointers, null, null);
        }
		/// <summary>
        /// Vrátí souhrn událostí (IEventItem), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <param name="maxCount">Počet výstupních položek, které mi postačují</param>
        /// <returns></returns>
        public List<KeyValuePair<DataPointerStr, IEventItem>> FindEvents(IEnumerable<DataPointerStr> pointers, Int32? maxCount)
        {
            return this.FindEvents(pointers, null, maxCount);
        }
		/// <summary>
        /// Vrátí souhrn událostí (IEventItem), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <param name="filter">Filtr, které položky chci dávat do výstupu</param>
        /// <param name="maxCount">Počet výstupních položek, které mi postačují</param>
        /// <returns></returns>
        public List<KeyValuePair<DataPointerStr, IEventItem>> FindEvents(IEnumerable<DataPointerStr> pointers, Func<IEventItem, bool> filter, Int32? maxCount)
        {
            List<KeyValuePair<DataPointerStr, IEventItem>> events = new List<KeyValuePair<DataPointerStr, IEventItem>>();
            foreach (DataPointerStr pointer in pointers)
            {
                NodeInfo node = this.FindNode(pointer.Row);
                if (node != null)
                {
                    IDataElement element = node.FindElement(pointer.Element);
                    if (element is EventInfo)
                    {
                        EventInfo eventInfo = element as EventInfo;
                        if (eventInfo != null && (filter == null || filter(eventInfo.EventItem)))
                            events.Add(new KeyValuePair<DataPointerStr, IEventItem>(pointer, eventInfo.EventItem));
                    }
                }
                if (maxCount.HasValue && events.Count > maxCount.Value) break;
            }
            return events;
        }
		/// <summary>
		/// Vrátí hledanou událost (EventInfo), pro zadaný pointer
		/// </summary>
		/// <param name="pointers"></param>
		/// <returns></returns>
		internal EventInfo FindEventInfo(DataPointerStr pointer)
		{
			NodeInfo node = this.FindNode(pointer.Row);
			if (node != null)
			{
				IDataElement element = node.FindElement(pointer.Element);
				if (element != null && element is EventInfo)
					return element as EventInfo;
			}
			return null;
		}
		/// <summary>
        /// Vrátí souhrn událostí (EventInfo), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <returns></returns>
        internal List<KeyValuePair<DataPointerStr, EventInfo>> FindEventInfos(IEnumerable<DataPointerStr> pointers)
        {
            return this.FindEventInfos(pointers, null, null);
        }
        /// <summary>
        /// Vrátí souhrn událostí (EventInfo), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <param name="maxCount">Počet výstupních položek, které mi postačují</param>
        /// <returns></returns>
        internal List<KeyValuePair<DataPointerStr, EventInfo>> FindEventInfos(IEnumerable<DataPointerStr> pointers, Int32? maxCount)
        {
            return this.FindEventInfos(pointers, null, maxCount);
        }
		/// <summary>
        /// Vrátí souhrn událostí (EventInfo), které odpovídají daným pointerům z grafické vrstvy.
        /// </summary>
        /// <param name="pointers"></param>
        /// <param name="filter">Filtr, které položky chci dávat do výstupu</param>
        /// <param name="maxCount">Počet výstupních položek, které mi postačují</param>
        /// <returns></returns>
        internal List<KeyValuePair<DataPointerStr, EventInfo>> FindEventInfos(IEnumerable<DataPointerStr> pointers, Func<IEventItem, bool> filter, Int32? maxCount)
        {
            List<KeyValuePair<DataPointerStr, EventInfo>> events = new List<KeyValuePair<DataPointerStr, EventInfo>>();
            foreach (DataPointerStr pointer in pointers)
            {
                NodeInfo node = this.FindNode(pointer.Row);
                if (node != null)
                {
                    IDataElement element = node.FindElement(pointer.Element);
                    if (element != null && element is EventInfo)
                    {
                        EventInfo eventInfo = element as EventInfo;
                        if (eventInfo != null && (filter == null || filter(eventInfo.EventItem)))
                            events.Add(new KeyValuePair<DataPointerStr, EventInfo>(pointer, eventInfo));
                    }
                }
                if (maxCount.HasValue && events.Count > maxCount.Value) break;
            }
            return events;
        }
        /// <summary>
        /// Vrátí souhrn událostí (IEventItem), které jsou nyní načteny ve všech procesech, a které vyhovují zadanému filtru (predicate).
        /// </summary>
        /// <param name="predicate">Filtr na jednotlivé události (predikát)</param>
        /// <returns></returns>
        public List<IEventItem> GetLoadedEvents(Func<IEventItem, bool> predicate)
        {
            List<IEventItem> events = new List<IEventItem>();
            foreach (ProcessInfo processInfo in this.Processes)
                events.AddRange(processInfo.GetLoadedEvents(predicate));
            return events;
        }
        /// <summary>
        /// Seznam procesů, které tento datový balík eviduje.
        /// Jde o procesy všech serverů.
        /// (Procesy jednoho serveru lze najít v property ServerInfo.Processes)
        /// </summary>
        public IEnumerable<ProcessInfo> Processes
        {
            get
            {
                List<ProcessInfo> result = new List<ProcessInfo>();
                foreach (ServerInfo server in this.Servers)
                    result.AddRange(server.Processes);
                return result;
            }
        }
        /// <summary>
        /// Vyhledá a vrátí procesy, které odpovídají dané podmínce.
        /// Jde o procesy ze všech serverů.
        /// (Procesy jednoho serveru lze najít v property this.Servers[].Processes)
        /// </summary>
        public IEnumerable<ProcessInfo> FindProcesses(Func<ProcessInfo, bool> predicate)
        {
            List<ProcessInfo> result = new List<ProcessInfo>();
            foreach (ServerInfo server in this.Servers)
                result.AddRange(server.Processes.Where(predicate));
            return result;
        }
		/// <summary>
		/// Načte nejbližší jeden trace soubor v daném směru.
        /// Pokud v daném směru nenajde žádný soubor, který je třeba načíst, vrátí null.
        /// Pokud najde trace soubor k načtení, aktualizuje zadaný TimeVector.
		/// </summary>
		/// <param name="vector">Časový vektor. Hledá nejbližší nenačtený soubor. Po načtení souboru aktualizuje vektor tak, že ukazuje na začátek (ToFuture) / konec (ToHistory) načteného souboru.</param>
        /// <param name="statusText">Text, který se má vložit do StatusBaru po načtení souboru. null = nic. Běžně se vkládá text Ready.</param>
        public TraceInfo PrepareNextTraceFiles(ref TimeVector vector, string statusText)
		{
            if (vector.IsEmpty) return null;
            if (this.Traces == null || this.Traces.Count == 0) return null;

            // Podíváme se, zda existuje vhodný soubor k načtení:
            TraceInfo traceInfo = null;
            foreach (TraceInfo trace in this.Traces)
			{
				if (trace.ContentProcessed) continue;         // tento soubor už je zpracován?
                if (trace.IsIncorrect) continue;              // tento soubor nejde zpracovat, obsahuje chybu
				switch (vector.Direction)
				{
					case TimeRange.TimeDirection.ToFuture:
                        if (trace.Time.End > vector.Date)
                        {   // Soubor leží ve správném směru od vektoru (rovnítko sem nepatří, pokud soubor končí v požadovaném okamžiku, pak nás nezajímá):
                            // Pokud dosud nemáme žádný, anebo nějaký máme ale tento aktuální má menší datum začátku, tak vezmeme ten aktuální:
                            if (traceInfo == null || (traceInfo != null && trace.Time.Begin < traceInfo.Time.Begin))
                                traceInfo = trace;
                        }
                        break;
					case TimeRange.TimeDirection.ToHistory:
                        if (trace.Time.Begin < vector.Date)
                        {
                            if (traceInfo == null || (traceInfo != null && trace.Time.End > traceInfo.Time.End))
                                traceInfo = trace;
                        }
                        break;
                }
			}
            if (traceInfo == null) return null;

            // Našli jsme soubor, který budeme načítat:
			this.TraceCheckProcessed(traceInfo, null, null);

            // Posunutí TimeVectoru, pokud je to vhodné:
            switch (vector.Direction)
            {
                case TimeRange.TimeDirection.ToFuture:
                    if (traceInfo.Time.Begin > vector.Date)
                        // Pokud jsme načetli soubor, který začíná později, než je zadaný vektor, upravíme vektor podle souboru:
                        vector = new TimeVector(traceInfo.Time.Begin, TimeRange.TimeDirection.ToFuture);
                    break;
                case TimeRange.TimeDirection.ToHistory:
                    if (traceInfo.Time.End < vector.Date)
                        vector = new TimeVector(traceInfo.Time.End, TimeRange.TimeDirection.ToHistory);
                    break;
            }

            return traceInfo;
		}
		/// <summary>
        /// Aktuální počet zpracovaných souborů
        /// </summary>
        public int TraceProcessedCount
        {
            get
            {
                int cnt = 0;
                foreach (TraceInfo trace in this.Traces)
                {
                    if (trace.ContentProcessed)
                        cnt++;
                }
                return cnt;
            }
        }
        /// <summary>
        /// Aktuální počet procesů
        /// </summary>
        public int ProcessCount
        {
            get
            {
                int cnt = 0;
                foreach (ServerInfo serverInfo in this.Servers)
                    cnt += serverInfo.ProcessCount;
                return cnt;
            }
        }
        /// <summary>
        /// Aktuální počet událostí (eventů)
        /// </summary>
        public int EventCount
        {
            get
            {
                int cnt = 0;
                foreach (ServerInfo serverInfo in this.Servers)
                    cnt += serverInfo.EventCount;
                return cnt;
            }
        }
        #endregion
        #region Vyhledání složitějších dat, služby pro externí funkce
        /// <summary>
        /// Metoda najde a vátí seznam procesů, jejichž některý event (byť i jeden jediný) leží v daném čase a vyhovuje dané podmínce.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="predicate">Podmínka, jejímž vstupem je EventInfo a výstupem Boolean.</param>
        /// <returns></returns>
        public List<ProcessInfo> FindListProcessesWithEventInTime(DateTime time, TraceEventTimeSelect timeSelect, Func<EventInfo, bool> predicate)
        {
            List<ProcessInfo> result = this.FindListProcessesWithEvent(ev => ev.TimeBySelect(timeSelect).Contain(time) && predicate(ev));
            return result;
        }
        /// <summary>
        /// Metoda najde a vátí seznam procesů, jejichž některý event (byť i jeden jediný) vyhovuje dané podmínce.
        /// </summary>
        /// <param name="predicate">Podmínka, jejímž vstupem je EventInfo a výstupem Boolean.</param>
        /// <returns></returns>
        public List<ProcessInfo> FindListProcessesWithEvent(Func<EventInfo, bool> predicate)
        {
            List<ProcessInfo> result = new List<ProcessInfo>();
            if (predicate != null)
            {
                foreach (ProcessInfo processInfo in this.Processes)
                {
                    if (processInfo.Events.Any(predicate))
                        result.Add(processInfo);
                }
            }
            return result;
        }
        /// <summary>
        /// Metoda najde a vátí seznam procesů, jejichž některý event (byť i jeden jediný) leží v daném čase a vyhovuje dané podmínce.
        /// </summary>
        /// <param name="time"></param>
        /// <param name="predicate">Podmínka, jejímž vstupem je EventInfo a výstupem Boolean.</param>
        /// <returns></returns>
        public ProcessInfo FindFirstOrDefaultProcessInfoWithEventInTime(DateTime time, TraceEventTimeSelect timeSelect, Func<EventInfo, bool> predicate)
        {
            ProcessInfo result = this.FindFirstOrDefaultProcessInfoWithEvent(ev => ev.TimeBySelect(timeSelect).Contain(time) && predicate(ev));
            return result;
        }
        /// <summary>
        /// Metoda najde a vátí seznam procesů, jejichž některý event (byť i jeden jediný) vyhovuje dané podmínce.
        /// </summary>
        /// <param name="predicate">Podmínka, jejímž vstupem je EventInfo a výstupem Boolean.</param>
        /// <returns></returns>
        public ProcessInfo FindFirstOrDefaultProcessInfoWithEvent(Func<EventInfo, bool> predicate)
        {
            ProcessInfo result = null;
            if (predicate != null)
            {
                foreach (ProcessInfo processInfo in this.Processes)
                {
                    if (processInfo.Events.Any(predicate))
                    {
                        result = processInfo;
                        break;
                    }
                }
            }
            return result;
        }
        #endregion
        #region Konverze stringu Datum;Čas na DateTime
        /// <summary>
        /// Ze vstupního textu ve formátu "yyyy/MM/dd;HH:mm:ss.fff" (například "2011/08/03;12:16:18.126") vrátí odpovídající DateTime.
        /// Vstupující string má mít délku nejméně 23 znaků.
        /// Vstupující string může obsahovat i více položek (prvků), akceptují se jen první dvě (s oddělovačem ";").
        /// Anebo vstupující text má odpovídat předpisu "d.M.yyyy H:mm:ss.fff" (bez středníku, s mezerou), pak se přečte tímto formátem.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static DateTime ConvertDateTime(string text)
        {
            DateTime result = DateTime.MinValue;
            if (!String.IsNullOrEmpty(text))
            {
                string[] items = text.Split(';');
                if (items.Length == 1 && items[0].Contains(" "))
                {   // Jedna položka, obsahující mezeru: předpokládáme formát databáze ("d.M.yyyy H:mm:ss.fff"):
                    if (items[0].Length >= 15)
                        result = ConvertDateTimeOne(items[0]);
                }
                else if (items.Length >= 2)
                {
                    string time = items[0] + ";" + items[1];
                    if (time.Length >= 23)
                        result = ConvertDateTimeOne(time.Substring(0, 23));
                }
            }
            return result;
        }
        /// <summary>
        /// Konvertuje daný text na datum a čas
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static DateTime ConvertDateTimeOne(string text)
        {
            DateTime result = DateTime.MinValue;
            bool parsed = false;

            if (!parsed && text.Contains("-") && text.Contains(":"))
                parsed = DateTime.TryParseExact(text, "F", _DateTimeFmtTraceFmtSys, System.Globalization.DateTimeStyles.NoCurrentDateDefault, out result);

            if (!parsed && text.Contains(";"))
                parsed = DateTime.TryParseExact(text, "F", _DateTimeFmtTraceFmtAs, System.Globalization.DateTimeStyles.NoCurrentDateDefault, out result);

            if (!parsed && text.Contains("/"))
                parsed = DateTime.TryParseExact(text, "F", _DateTimeFmtTraceFmtD2, System.Globalization.DateTimeStyles.NoCurrentDateDefault, out result);

            if (!parsed && text.Contains("."))
                parsed = DateTime.TryParseExact(text, "F", _DateTimeFmtTraceFmtDb, System.Globalization.DateTimeStyles.NoCurrentDateDefault, out result);

            if (!parsed)
            {
                string[] items = text.Split(' ', '-', ',', ';', '.', ':', '/');
                Int32?[] values = ConvertStringsToInts(items);
                int count = items.Length;

                int year = 0;
                int month = 0;
                int day = 0;
                
                // Autodetekce YMD:      5/30/2012 7:48:09 AM
                int d1 = ConvertGetInt(items, 0, 0);
                int d2 = ConvertGetInt(items, 1, 0);
                int d3 = ConvertGetInt(items, 2, 0);
                if (d1 > 31)
                {   // YMD
                    year = d1;
                    month = d2;
                    day = d3;
                }
                else if (d3 > 31)
                {   // MDY a DMY
                    if (d1 > 12)
                    {   // DMY
                        day = d1;
                        month = d2;
                    }
                    else if (d2 > 12)
                    {   // MDY
                        month = d1;
                        day = d2;
                    }
                    else
                    {   // D a M je obojí do 12 (například 6.8.2012 nebo 8/2/2012), odhadnu pořadí podle oddělovače: tečka je DMY, ostatní (lomítko, pomlčka) je MDY:
                        if (text.Substring(0, 6).Contains('.'))
                        {   // 6.8. = D.M.:
                            day = d1;
                            month = d2;
                        }
                        else
                        {   // 8/2/ = M/D/:
                            month = d1;
                            day = d2;
                        }
                    }
                    year = d3;
                }
                else
                {   // elze určit, kde je rok: přikláním se ke strojové variantě Y-M-D:
                    year = d1;
                    month = d2;
                    day = d3;
                }
                if (year <= 100) year += 2000;

                int hour = ConvertGetInt(items, 3, 0, 0, 23);
                int minute = ConvertGetInt(items, 4, 0, 0, 59);
                int second = ConvertGetInt(items, 5, 0, 0, 59);
                int milisecond = ConvertGetInt(items, 6, 0, 0, 999);

                bool isPm = (items.Any(item => item.ToUpper() == "PM"));
                if (isPm && hour < 12) hour += 12;

                try
                {
                    result = new DateTime(year, month, day, hour, minute, second, milisecond);
                }
                catch (Exception exc)
                {
                    throw new ArgumentException("DateTime is in bad format: " + text, exc);
                }
            }

            return result;
        }

        private static int ConvertGetInt(string[] items, int index, int defaultValue, int min, int max)
        {
            int result = ConvertGetInt(items, index, defaultValue);
            if (result < min) return min;
            if (result > max) return max;
            return result;
        }
        private static int ConvertGetInt(string[] items, int index, int defaultValue)
        {
            int result;
            if (index < items.Length)
            {
                string item = items[index];
                if (item.Length > 0 && Int32.TryParse(item, out result))
                    return result;
            }
            return defaultValue;
        }
        /// <summary>
        /// Převede pole stringů do pole intů.
        /// Který string nepůjde převést, na jeho pozici dá null.
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private static int?[] ConvertStringsToInts(string[] items)
        {
            int len = items.Length;
            int?[] result = new int?[len];
            for (int i = 0; i < len; i++)
            {
                int value;
                if (Int32.TryParse(items[i], out value))
                    result[i] = value;
                else
                    result[i] = null;
            }
            return result;
        }

        /// <summary>
        /// Ze vstupního datumu a textu (čas) ve formátu "HH:mm:ss.fff" (například 2011-08-03, "12:16:18.126") vrátí odpovídající DateTime.
        /// Vstupující string má mít délku nejméně 8 znaků (čas bez milisekund) nebo 12 (s milisekundami).
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static DateTime ConvertDateTime(DateTime date, string time)
		{
			DateTime result = date;
			if (!String.IsNullOrEmpty(time))
			{
                int h, m, s, f;
                string t = time.Trim();
                h = _ConvertDateTimeGet(ref t, ":", 0, 23);
                m = _ConvertDateTimeGet(ref t, ":", 0, 59);
                s = _ConvertDateTimeGet(ref t, ".", 0, 59);
                f = _ConvertDateTimeGet(ref t, "", 0, 999);

                result = result.Date + new TimeSpan(0, h, m, s, f);
			}
			return result;
		}
        /// <summary>
        /// Odděluje z daného textu část před oddělovačem, převádí ji na int a zarovnává do mezí min-max (včetně).
        /// </summary>
        /// <param name="text"></param>
        /// <param name="delimiter"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        private static int _ConvertDateTimeGet(ref string text, string delimiter, int min, int max)
        {
            if (text.IsEmpty()) return min;

            string data = null;
            if (delimiter.Length > 0)
            {
                int len = text.Length;
                if (len == 0) return min;
                int pos = text.IndexOf(delimiter);
                if (pos < 0) pos = len;
                data = (pos == 0 ? "" : (pos >= len ? text : text.Substring(0, pos)));
                text = ((pos < (len - 1)) ? text.Substring(pos + 1) : "");
            }
            else
            {
                data = text;
                text = "";
            }

            // String => Int
            int value;
            if (!Int32.TryParse(data, out value)) return min;
            if (value < min) return min;
            if (value > max) return max;
            return value;
        }
		/// <summary>
        /// Formátovací objekt pro datum a čas, jak je ukládán v Trace: "2011/08/03;12:16:18.126".
        /// Odpovídající výraz je připraven pro format string "F" (v property FullDateTimePattern).
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo _DateTimeFmtTraceFmtAs
        {
            get
            {
                if (__DateTimeFmtTraceFmtAs == null)
                {
                    System.Globalization.DateTimeFormatInfo dtfi = new System.Globalization.DateTimeFormatInfo();
                    dtfi.FullDateTimePattern = "yyyy/MM/dd;HH:mm:ss.fff";
                    __DateTimeFmtTraceFmtAs = dtfi;
                }
                return __DateTimeFmtTraceFmtAs;
            }
        }
        /// <summary>
        /// Úložiště formátovacího objektu pro datum a čas
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo __DateTimeFmtTraceFmtAs;
        /// <summary>
        /// Formátovací objekt pro datum a čas, jak je ukládán v Db: "d.M.yyyy H:mm:ss.fff".
        /// Odpovídající výraz je připraven pro format string "F" (v property FullDateTimePattern).
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo _DateTimeFmtTraceFmtDb
        {
            get
            {
                if (__DateTimeFmtTraceFmtDb == null)
                {
                    System.Globalization.DateTimeFormatInfo dtfi = new System.Globalization.DateTimeFormatInfo();
                    dtfi.FullDateTimePattern = "d.M.yyyy H:mm:ss";
                    __DateTimeFmtTraceFmtDb = dtfi;
                }
                return __DateTimeFmtTraceFmtDb;
            }
        }
        /// <summary>
        /// Úložiště formátovacího objektu pro datum a čas
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo __DateTimeFmtTraceFmtDb;
        /// <summary>
        /// Formátovací objekt pro datum a čas, jak je ukládán v Db verze 2: "d/M/yyyy H:mm:ss AM".
        /// Odpovídající výraz je připraven pro format string "F" (v property FullDateTimePattern).
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo _DateTimeFmtTraceFmtD2
        {
            get
            {
                if (__DateTimeFmtTraceFmtD2 == null)
                {
                    System.Globalization.DateTimeFormatInfo dtfi = new System.Globalization.DateTimeFormatInfo();
                    dtfi.FullDateTimePattern = "M/d/yyyy h:mm:ss tt";
                    dtfi.AMDesignator = "AM";
                    dtfi.PMDesignator = "PM";
                    __DateTimeFmtTraceFmtD2 = dtfi;
                }
                return __DateTimeFmtTraceFmtD2;
            }
        }
        /// <summary>
        /// Úložiště formátovacího objektu pro datum a čas
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo __DateTimeFmtTraceFmtD2;

        /// <summary>
        /// Formátovací objekt pro datum a čas, jak je ukládán systémově: "yyyy-MM-dd HH:mm:ss".
        /// Odpovídající výraz je připraven pro format string "F" (v property FullDateTimePattern).
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo _DateTimeFmtTraceFmtSys
        {
            get
            {
                if (__DateTimeFmtTraceFmtSys == null)
                {
                    System.Globalization.DateTimeFormatInfo dtfi = new System.Globalization.DateTimeFormatInfo();
                    dtfi.FullDateTimePattern = "yyyy-MM-dd HH:mm:ss";
                    __DateTimeFmtTraceFmtSys = dtfi;
                }
                return __DateTimeFmtTraceFmtSys;
            }
        }
        /// <summary>
        /// Úložiště formátovacího objektu pro datum a čas
        /// </summary>
        private static System.Globalization.DateTimeFormatInfo __DateTimeFmtTraceFmtSys;

        #endregion
        #region IDisposable Members
        public void Dispose()
        {
            this._FileWatchStop();

            this._DisposeItems();

			this.Servers.Dispose();
            this.Traces.Dispose();
            this._NextIDs = null;
            this.TraceColors = null;
        }
        private void _DisposeItems()
        {
            this.NodeRoot.Dispose();

            foreach (TraceInfo trace in this.Traces)
                trace.Dispose();

            foreach (ServerInfo server in this.Servers)
                server.Dispose();
            
        }
        #endregion

    }
    #endregion
    #region TraceInfo : data jednoho trace souboru (csv)
    /// <summary>
    /// Info o jednom trace souboru.
    /// Key = plné jméno souboru
    /// </summary>
    public class TraceInfo : BaseInfo, IDataElement, IColorDeclarator
    {
        #region Konstrukce a property
        public TraceInfo(ServerInfo server, System.IO.FileInfo fileInfo, Type traceReaderType, TraceReaderCheckContentArgs contentArgs) :
            base(server.Main, fileInfo.FullName)
        {
            this.Server = server;
            this.FileInfo = fileInfo;
            this.TraceReaderType = traceReaderType;
            this.ElementTexts = contentArgs.ElementTexts;
            this.ElementTooltipStandard = contentArgs.ElementTooltip;
            this.TraceDate = contentArgs.TraceDate;
            this.Time = contentArgs.ContentTime;
            this.TraceType = contentArgs.TraceType;
            this.ContentProcessed = false;
            this.ItemsProcessed = -1;
			this.UserData = new UserData();
        }
        public override void Dispose()
        {
            base.Dispose();

            this.Server = null;
            this.FileInfo = null;
            this.TraceReaderType = null;
            this.ElementTexts = null;
            this.TraceReaderException = null;
        }
        public override string ToString()
        {
            return this.FileInfo.Name + "; Containing " + this.ItemsProcessed.ToString() + " lines, time=" + this.Time.ToString();
        }
        /// <summary>
        /// Zámek zajišťující unikátní přístup k výkonnému jádru metody _ProcessContentLock()
        /// </summary>
        private object _ProcessLock = new object();
        /// <summary>
        /// Číslo třídy pro GID
        /// </summary>
        public override int ClassNr { get { return Constants.ClassNumberTrace; } }
        /// <summary>
        /// Info o aplikačním serveru, do něhož tento soubor patří
        /// </summary>
        public ServerInfo Server { get; private set; }
        /// <summary>
        /// Datum odvozené ze jména (nebo prvního řádku) souboru.
        /// Čas je 00:00.
        /// </summary>
        public DateTime TraceDate { get; private set; }
        /// <summary>
        /// FileInfo o souboru (informace z Filesystému)
        /// </summary>
        public System.IO.FileInfo FileInfo { get; private set; }
        /// <summary>
        /// Velikost souboru (Byte)
        /// </summary>
        public long FileSize { get { return (this.FileInfo == null || !this.FileInfo.Exists ? 0L : this.FileInfo.Length); } }
        /// <summary>
        /// Vrátí název adresáře + souboru s příponou, ale adresář jen jeden
        /// </summary>
        public string FileDirName { get { return System.IO.Path.Combine(this.FileInfo.Directory.Name, this.FileInfo.Name); } }
        /// <summary>
        /// Vrátí plný název souboru
        /// </summary>
        public string FullFileName { get { return this.FileInfo.FullName; } }
        /// <summary>
        /// Typ readeru, který zajišťuje načítání dat z tohoto souboru. Reader se sám přihlásil do veřejné soutěže o načítání tohoto souboru, a vyhrál.
        /// </summary>
        public Type TraceReaderType { get; private set; }
        /// <summary>
        /// Texty vypisované uvnitř elementu.
        /// Definuje se jich celá sada různých variant, lišících se v zásadě délkou textu.
        /// Graf vybere ten text, který se do elementu vejde vepsat (šířka elementu závisí na jeho velikosti a měřítku časové osy).
        /// </summary>
        public string[] ElementTexts { get; private set; }
        /// <summary>
        /// Tooltip zobrazovaný v řádku elementu za tento soubor.
        /// Uplatní se jen tehdy, když žádný jiný reader pro tento souboru nebude mít větší prioritu.
        /// </summary>
        public string ElementTooltipStandard { get; private set; }
        /// <summary>
        /// Časový rozsah souboru od prvního do posledního záznamu
        /// </summary>
        public TimeRange Time { get; private set; }
		/// <summary>
		/// Typ trace souboru. 
		/// Je určen při detekci trace souboru (v jeho readeru), a uložen do TraceInfo při jeho zakládání.
		/// Odsud se přenáší do všech Node, které se zakládají pro data tohoto trace souboru.
		/// Následně se vyhodnocuje při načítání elementů a SubNode pro daný Node, a filtrují se podle něj jen ty Trace soubory, které obsahují relevantní data.
		/// </summary>
		public string TraceType { get; private set; }
        /// <summary>
        /// Libovolná uživatelská data, jejich život jde od první detekce jména a obsahu souboru skrz všechny funkce (zpracování obsahu) 
        /// </summary>
        public UserData UserData { get; private set; }
        /// <summary>
		/// Pořadí trace souboru v časové ose, je určeno po načtení záhlaví všech trace souborů.
		/// Je číslováno od 0 v rámci každého parent objektu = Server.
		/// </summary>
		public int TraceOrder { get; set; }
        /// <summary>
        /// Obsah tohoto souboru byl zpracován do this.Main (rozebrán na události, thready, uživatele atd)
        /// </summary>
        public bool ContentProcessed { get; private set; }
        /// <summary>
        /// Obsahuje true, pokud při načítání tohoto trace došlo k chybě (this.TraceReaderException != null)
        /// </summary>
        public bool IsIncorrect { get { return (this.TraceReaderException != null); } }
        /// <summary>
        /// Počet řádek, které byly předány do Main při posledním načítání
        /// </summary>
        public int ItemsProcessed { get; private set; }
        /// <summary>
        /// Skupina elementů. Odvozuje se z this.Server.ElementGroup.
        /// </summary>
        public int ElementGroup { get { return this.Server.ElementGroup; } }
        /// <summary>
        /// Pokud při načítání dat dojde k chybě, bude zachycena zde.
        /// </summary>
        public Exception TraceReaderException { get; private set; }
        /// <summary>
        /// Komparátor podle data počátku času obsahu souboru (ContentTime.Begin)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByContentBegin(TraceInfo a, TraceInfo b)
        {
            return a.Time.Begin.CompareTo(b.Time.Begin);
        }
        /// <summary>
        /// Obsahuje průměrný čas jednoho souboru
        /// </summary>
        internal TimeSpan LoadedAverageTime
        {
            get
            {
                return TimeSpan.FromSeconds(this.Time.Seconds);
            }
        }
        /// <summary>
        /// Pomáhá určit průměrný čas jednoho fyzického souboru.
        /// </summary>
        /// <param name="fileCnt"></param>
        /// <param name="secSum"></param>
        internal void CalculateLoadedAverageTime(ref int fileCnt, ref double secSum)
        {
            if (this.Time.IsEmpty) return;
            fileCnt++;
            secSum += this.Time.Seconds;
        }
        /// <summary>
        /// Aktuální objekt ITraceReader, který právě nyní načítá data z trace souboru.
        /// Odkaz na tento objekt sem umísťuje instance třídy TraceInfo, při načítání dat. Po načtení dat je zde null.
        /// </summary>
        public ITraceReader CurrentTraceReader { get { return this.Main.CurrentTraceReader; } protected set { this.Main.CurrentTraceReader = value; } }
        /// <summary>
        /// Obsahuje aktuální Trace soubor, který se načítá
        /// </summary>
        public TraceInfo CurrentTraceInfo { get { return this.Main.CurrentTraceInfo; } protected set { this.Main.CurrentTraceInfo = value; } }
        /// <summary>
        /// Příznak, že TraceReader, který načetl data tohoto trace, implementuje rozhraní ITraceReaderWithAutoTimeShift = tedy že dokáže provést autodetekci TimeShiftu tohoto souboru.
        /// </summary>
        public bool CurrentTraceReaderCanProcessTimeShift { get; protected set; }
        /// <summary>
        /// Příznak, že pro tento soubor proběhla autodetekce TimeShiftu, a další už probíhat nemusí.
        /// </summary>
        public bool AutoTimeShiftProcessed { get; protected set; }
        #endregion
        #region Zobrazování aktuální barvy a tooltipu + chyby při načítání obsahu souboru
		/// <summary>
		/// Obsahuje aktuální barevnou skupinu.
		/// Reaguje na stav načtení obsahu.
		/// </summary>
		public string CurrentColorGroup
		{
			get
			{
				string colorGroup = "TraceFile";
				if (!this.ContentProcessed)
					colorGroup += ".New";
				else if (this.TraceReaderException == null)
					colorGroup += ".Prepared";
				else
					colorGroup += ".Error";

				return colorGroup;
			}
		}
        /// <summary>
        /// Obsahuje aktuální barvu elementu TraceInfo. Odráží stav načtení + chybu při načítání.
        /// </summary>
        public Color CurrentBackColor
        {
            get { return this.Main.GetColor(CurrentColorGroup, this.Server.Id); }
        }
        /// <summary>
        /// Obsahuje ToolTip (standardní + Error)
        /// </summary>
        public string ToolTip
        {
            get
            {
                string toolTip = this.ElementTooltipStandard;
                if (this.TraceReaderException != null)
                    toolTip += Environment.NewLine +
                        Environment.NewLine +
                        this.TraceReaderException.Message + Environment.NewLine +
                        Environment.NewLine +
                        this.TraceReaderException.StackTrace;
                return toolTip;
            }
        }
        #endregion
        #region Načtení kompletního obsahu souboru (řádky) do objektu, scanování souboru, ukládání logických dat do Main
        /// <summary>
        /// Metoda zajistí, že bude načten obsah tohoto souboru, a jeho data budou předána do Main k uložení do systému.
        /// </summary>
		/// <param name="traceType">Typ dat v trace.
		/// Pokud je zadáno, pak se obsah souboru načte jen tehdy, když odpovídá jeho TraceType zadanému.
		/// Pokud je zadáno null, anebo tento soubor nemá uveden svůj TraceType, pak se načte.
		/// Viz metoda IsTraceType().</param>
        internal bool CheckProcessed(string traceType)
        {
            if (!this.NeedProcessForTraceType(traceType))
                return false;

            this.ProcessContent();
            return true;
        }
        /// <summary>
        /// Metoda vrátí true, pokud tento Trace soubor se má zpracovávat kvůli řádku obsahujícímu daný typ dat
        /// </summary>
        /// <param name="traceType">Typ dat v trace.
        /// Pokud je zadáno, pak se obsah souboru načte jen tehdy, když odpovídá jeho TraceType zadanému.
        /// Pokud je zadáno null, anebo tento soubor nemá uveden svůj TraceType, pak se načte.
        /// Viz metoda IsTraceType().</param>
        /// <returns></returns>
        internal bool NeedProcessForTraceType(string traceType)
        {
            return (!this.ContentProcessed && this.IsTraceType(traceType));
        }
		/// <summary>
		/// Vrátí true, pokud this objekt je shodného typu (TraceType) jako je zadaný typ (parametr).
		/// Vrátí true i tehdy, když je zadáno null nebo když this.TraceType je null.
		/// </summary>
		/// <param name="traceType"></param>
		/// <returns></returns>
		internal bool IsTraceType(string traceType)
		{
			if (traceType == null || this.TraceType == null)
				return true;
			return (traceType == this.TraceType);
		}
        /// <summary>
        /// Načte obsah souboru a současně jej rovnou zpracovává do nadřízené logické vrstvy.
        /// Toto je preferovaná metoda z hlediska šetření paměti a zrychlení výkonu.
        /// Po skončení načítání zajistí překreslení řádku v grafické vrstvě (reaguje se tak na změnu stavu = barvy)
        /// </summary>
        internal bool ProcessContent()
        {
			bool processed = this._ProcessContentRead();
            this.Main.CallVisualDataChangedReDrawRow(this.Main.NodeTrace.GId, false);
            return processed;
        }
        /// <summary>
        /// Projde obsah souboru a současně jej rovnou zpracovává do nadřízené logické vrstvy.
        /// Toto je preferovaná metoda z hlediska VÝRAZNÉHO šetření paměti a zrychlení výkonu.
        /// Tato metoda prioritně využívá scanování souboru pomocí interface ITraceScanner, metodou ITraceScanner.ScanContent(), která načítaná data nijak neupravuje a ani neukládá do StringHeap.
        /// Pokud ale reader souboru neimplementuje interface ITraceScanner, pak se využívá běžná metoda ITraceReader.ProcessContent(), která z dat v souboru vytváří objekty IEventEdge nebo IEventItem,
        /// tyto objekty jsou předány do procesní metody (parametr processItem).
        /// </summary>
        public bool ScanContent(Action<object, TraceInfo> processObject)
        {
            return this._ProcessContentScan(ProcessContentMode.ScanObjects, null, null, processObject);
        }
        /// <summary>
		/// Načte obsah souboru a současně jej rovnou zpracovává do nadřízené logické vrstvy.
        /// Toto je preferovaná metoda z hlediska šetření paměti a zrychlení výkonu, pokud potřebujeme vytvářet konsolidované objekty, které implementují interface IEventEdge nebo IEventItem.
		/// </summary>
        public bool ScanContent(Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent)
		{
            return this._ProcessContentScan(ProcessContentMode.ScanItems, processEdge, processEvent, null);
		}
		/// <summary>
		/// Zajistí načtení dat z trace a jejich uložení do systému
		/// </summary>
		private bool _ProcessContentRead()
		{
			this.ContentProcessed = false;
			this.ItemsProcessed = 0;

            bool processed = this._ProcessContent(ProcessContentMode.ReadItems, this._ProcessItem, this._ProcessEvent, null);

            if (processed)
    			this.ContentProcessed = true;

            return processed;
		}
		/// <summary>
		/// Zajistí načtení dat z trace a jejich předání do určených metod, neukládá data do systému
		/// </summary>
		/// <param name="processEdge"></param>
		/// <param name="processEvent"></param>
        private bool _ProcessContentScan(ProcessContentMode processMode, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent, Action<object, TraceInfo> processObject)
		{
			int itemsProcessed = this.ItemsProcessed;       // Zazálohuji si, hodnotu pak vrátíme: zdejší funkce Scan fyzicky nenačítá data.
			this.ItemsProcessed = 0;
            bool processed = this._ProcessContent(processMode, processEdge, processEvent, processObject);
			this.ItemsProcessed = itemsProcessed;

            return processed;
        }
		/// <summary>
        /// Načte obsah souboru a současně jej rovnou zpracovává do nadřízené logické vrstvy.
        /// Neřeší invalidaci elementu + nodu.
        /// </summary>
        private bool _ProcessContent(ProcessContentMode processMode, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent, Action<object, TraceInfo> processObject)
        {
            this.TraceReaderException = null;
            bool processed = false;

            if (this.FileInfo == null || !this.FileInfo.Exists)
                return processed;

            if (this.TraceReaderType == null)
                return processed;

            try
            {
                _ProcessContentLock(processMode, processEdge, processEvent, processObject, ref processed);
            }
            catch (Exception exc)
            {
                this.TraceReaderException = exc;
                processed = false;
            }
            finally
            {
                if (processMode == ProcessContentMode.ReadItems)
                {   // Pouze v režimu skutečného načítání obsahu do paměti pracujeme s odkazy, kterí se propisují do Main:
                    this.CurrentTraceInfo = null;
                    this.CurrentTraceReader = null;
                }
            }
            return processed;
        }
        /// <summary>
        /// Zpracuje obsah souboru.
        /// Pod zámkem.
        /// </summary>
        /// <param name="processMode">Režim zpracování</param>
        /// <param name="processEdge">Akce pro nalezenou událost IEventEdge</param>
        /// <param name="processEvent">Akce pro nalezenou událost IEventItem</param>
        /// <param name="processObject">Akce pro nalezenou libovolnou událost v režimu ScanObject</param>
        /// <param name="processed"></param>
        private void _ProcessContentLock(ProcessContentMode processMode, Action<IEventEdge, TraceInfo> processEdge, Action<IEventItem, TraceInfo> processEvent, Action<object, TraceInfo> processObject, ref bool processed)
        {
            lock (this._ProcessLock)
            {
                // Získám zcela nový objekt Readeru, otevřu si soubor a jdu na čtení dat:
                using (ITraceReader reader = Steward.GetServiceObject<ITraceReader>(this.TraceReaderType, false))
                using (System.IO.FileStream fs = new System.IO.FileStream(this.FileInfo.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                using (System.IO.StreamReader sr = new System.IO.StreamReader(fs, true))
                {
                    if (reader != null)
                    {
                        try
                        {
                            if (processMode == ProcessContentMode.ReadItems)
                            {
                                this.CurrentTraceReader = reader;    // Tím se reference na reader vepíše do Main, a je dostupná pro všechny instance, které se na Main dostanou.
                                this.CurrentTraceInfo = this;
                                this.AutoTimeShiftProcessed = false;
                            }

                            this.CurrentTraceReaderCanProcessTimeShift = (reader is ITraceReaderWithAutoTimeShift);
                            if (processMode == ProcessContentMode.ScanObjects && processObject != null)
                            {   // QuickScan:
                                bool readerIsScanner = (reader is ITraceScanner);
                                if (readerIsScanner)
                                {   // Via scanner:
                                    ITraceScanner scanner = reader as ITraceScanner;
                                    TraceReaderScanContentArgs scanArgs = new TraceReaderScanContentArgs(this.Main, this, sr, processMode, processObject);
                                    scanner.ScanContent(scanArgs);
                                }
                                else
                                {   // Via reader:
                                    TraceReaderProcessContentArgs readArgs = new TraceReaderProcessContentArgs(this.Main, this, sr, processMode, processObject, processObject);
                                    reader.ProcessContent(readArgs);
                                }
                            }
                            else
                            {   // Read nebo FullScan:
                                TraceReaderProcessContentArgs readArgs = new TraceReaderProcessContentArgs(this.Main, this, sr, processMode, processEdge, processEvent);
                                reader.ProcessContent(readArgs);
                            }
                            processed = true;
                        }
                        finally
                        {
                            if (processMode == ProcessContentMode.ReadItems)
                            {   // Pouze v režimu skutečného načítání obsahu do paměti pracujeme s odkazy, kterí se propisují do Main:
                                // V režimu Scan tyto property neměníme, protože souběžně může běžet Read, a ten si na tyto property hraje:
                                this.CurrentTraceInfo = null;
                                this.CurrentTraceReader = null;
                            }
                        }
                    }
                    sr.Close();
                    fs.Close();
                }
            }
        }
        /// <summary>
        /// Zahodí informace o tom, že soubor byl načten.
        /// Zahodí informace o chybách při načítání (příště se pokusí načíst obsah souboru znovu)
        /// </summary>
        internal void DropContent()
        {
            this.ItemsProcessed = -1;
            this.ContentProcessed = false;
            this.TraceReaderException = null;
            this.CurrentTraceReaderCanProcessTimeShift = false;
            this.AutoTimeShiftProcessed = false;
        }
        /// <summary>
        /// Metoda, kterou volá reader poté, když načte a sestaví jednu položku s daty ze vstupního souboru.
        /// Metodu volá prostřednictvím argumentu TraceReaderProcessContentArgs, jeho delegáta ProcessItem.
        /// </summary>
		/// <param name="eventEdge"></param>
        private void _ProcessItem(IEventEdge eventEdge, TraceData.TraceInfo trace)
        {
            if (this.Main.Document.ComplyProjectFilter(eventEdge.FilterKeyword))
            {
                this.Server.AddData(eventEdge);
            }
            this.ItemsProcessed++;
            eventEdge.Dispose();
        }
        /// <summary>
        /// Metoda, kterou volá reader poté, když načte a sestaví jednu položku s daty ze vstupního souboru.
        /// Metodu volá prostřednictvím argumentu TraceReaderProcessContentArgs, jeho delegáta ProcessItem.
        /// </summary>
		/// <param name="eventItem"></param>
        private void _ProcessEvent(IEventItem eventItem, TraceData.TraceInfo trace)
        {
			this.Server.AddData(eventItem);
            this.ItemsProcessed++;
        }
        #endregion
        #region Finalizace dat po načtení
        /// <summary>
        /// Metoda je volaná poté, kdy je do dat v paměti načten jeden nebo více dalších souborů (po dokončení dávky načítání, z metody MainInfo.LoadContentFinalize()).
        /// Metoda se volá pouze pro soubory, které již jsou načtené.
        /// Metoda je určená k tomu, aby si v případě potřeby porovnala data ve svém souboru podle souborů ostatních.
        /// Prvotně vznikla kvůli synchronizaci časového posunu: jakmile je v paměti načten trace klienta i trace serveru, pak si klient může určit svůj TimeShift.
        /// </summary>
        internal void LoadContentFinalize()
        {
            this.FinalizeTimeShift();
        }
        /// <summary>
        /// Finalizace po načtení obsahu dalších souborů: autodetekce časového posunu
        /// </summary>
        protected void FinalizeTimeShift()
        {
            if (!Steward.AppConfig.TraceAutoTimeShift) return;

            if (!this.CurrentTraceReaderCanProcessTimeShift) return;
            if (this.AutoTimeShiftProcessed) return;

            using (ITraceReader reader = Steward.GetServiceObject<ITraceReader>(this.TraceReaderType, false))
            {
                ITraceReaderWithAutoTimeShift atsReader = reader as ITraceReaderWithAutoTimeShift;
                if (atsReader != null)
                {
                    TraceReaderDetectTimeShift args = new TraceReaderDetectTimeShift(this.Main, this);
                    bool isProcessed = atsReader.DetectTimeShift(args);
                    this.AutoTimeShiftProcessed = isProcessed;
                }
            }
        }
        #endregion
        #region Explicitní implementace interface IDataElement
        bool IDataElement.ElementVisible { get { return (this.Time.Seconds > 0D); } }
        GID IDataElement.GId { get { return this.GId; } }
        TimeRange IDataElement.TimeRange { get { return this.Time; } set { } }
		float IDataElement.ElementHeightRatio { get { return 0f; } }
		bool IDataElement.BeginIsUncertain { get { return false; } }
        bool IDataElement.EndIsUncertain { get { return false; } }
		int IDataElement.SubTimeSort { get { return 0; } }
		bool IDataElement.FullRow { get { return false; } }
        bool IDataElement.HideZeroWidthElementRectangle { get { return false; } }
        float[] IDataElement.Fragments { get { return null; } }
        GraphLayerType IDataElement.ElementLayer { get { return GraphLayerType.ElementItemLayer; } }
        int IDataElement.ElementGroup { get { return this.ElementGroup; } }
        GraphElementShapeType IDataElement.ElementShapeType { get { return GraphElementShapeType.Rectangle; } }
        ElementProperties IDataElement.Properties { get { return ElementProperties.None; } }
        ElementActionType IDataElement.ActionOnDoubleClick { get { return ElementActionType.DefaultByRow; } }
        GraphElementHandler IDataElement.DoubleClick { get { return null; } }
        string IDataElement.ColorGroup { get { return this.CurrentColorGroup; } }
        Color IDataElement.BackColor { get { return this.CurrentBackColor; } set { } }
        Color IDataElement.FontColor { get { return Color.Empty; } }
        bool IDataElement.CaptionShowAllways { get { return true; } }
        Image IDataElement.ElementImage { get { return null; } }
        ContentAlignment IDataElement.ImagePivotElementPoint { get { return ContentAlignment.MiddleCenter; } }
        ContentAlignment IDataElement.ImagePivotImagePoint { get { return ContentAlignment.MiddleCenter; } }
        string IDataElement.ToolTipInfo { get { return this.ToolTip; } }
        string[] IDataElement.Captions { get { return this.ElementTexts; } }

        string IDataElement.GetToolTipInfo(IDataRow rowData) { return null; }
        string IDataElement.GetCaption(IDataRow rowData, float charCount) { return null; }
        void IDataElement.DrawElement(GraphElementPainterArgs args) { }

        #endregion
        #region Static servis
        /// <summary>
        /// Porovná dvě události z hlediska jejich počátečního času
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByBegin(TraceInfo a, TraceInfo b)
        {
            if (a == null && b == null) return 0;
            if (a == null && b != null) return -1;
            if (a != null && b == null) return 1;

            return DateTime.Compare(a.Time.Begin, b.Time.Begin);
        }
        #endregion
		#region IPlugin, IColorDeclarator Members : deklarace konkrétních barev pro názvy ColorGroups
		bool IPlugin.Active { get { return true; } }
		void IColorDeclarator.ColorDeclare(ColorDeclaratorArgs args)
		{
            args.Append("TraceFile", Color.Khaki);
			args.Append("TraceFile.New", Color.FromArgb(207, 234, 93));
			args.Append("TraceFile.Prepared", Color.Aquamarine);
			args.Append("TraceFile.Error", Color.FromArgb(250, 154, 183));
		}
		// Aby třída mohla být Pluginem, musí mít bezparametrický konstruktor:
		private TraceInfo() : base() { }
		#endregion
    }
    #endregion
    #region ServerInfo : data o jednom aplikačním serveru (všechny jeho trace soubory)
	/// <summary>
	/// Info o jednom aplikačním serveru.
	/// Key = Klíčové slovo serveru (adresář + fileRoot).Upper()
	/// </summary>
    public class ServerInfo : BaseInfo, IColorDeclarator
	{
		#region Konstrukce
		public ServerInfo(MainInfo main, string serverName)
			: base(main, serverName)
		{
            this.DupliciteEventKeyHandling = DupliciteEventKeyHandlingType.None;
            this.Traces = new CollectIdx<TraceInfo>();
            this.Processes = new CollectIdx<ProcessInfo>();
            this.TimeShiftList = new List<TraceServerTimeOffset>();
            this.TimeShiftDict = new Dictionary<int, TraceServerTimeOffset>();
		}
        public override void Dispose()
        {
            base.Dispose();

            if (this.Processes != null)
            {
                foreach (ProcessInfo process in this.Processes)
                    process.Dispose();
                this.Processes.Dispose();
            }
        }
		public override string ToString()
		{
            return "ServerId=" + this.Id.ToString() + "; Root=" + this.Key + "; Processes.Count = " + this.Processes.Count.ToString();
		}
        /// <summary>
        /// Název serveru (=Key)
        /// </summary>
        public string ServerName { get { return this.Key; } }
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public override int ClassNr { get { return Constants.ClassNumberServer; } }
        /// <summary>
        /// Sumární čas trace souborů na serveru. Před tím ani potom nic není. Tento čas je určen při finalizaci načítání trace souborů
        /// </summary>
        public TimeRange Time { get; internal set; }
        /// <summary>
        /// Souhrn všech trace souborů, které zapisují do tohoto serveru.
        /// Key hodnota = plné jméno souboru.
        /// </summary>
        public CollectIdx<TraceInfo> Traces { get; private set; }
        /// <summary>
        /// Souhrn procesů. Klíč procesu přichází zvenčí v události (ITraceItem.ProcessKey).
        /// Jde o logicky související sadu dat.
        /// V jednom procesu je řada událostí. Jedna událost nemůže být ve více procesech.
        /// Proces se může zobrazovat jako element v některých nodech (představuje časový úsek sebe jako procesu), například v Root.Uživatelé.Uživatel.
        /// Elementy procesu mají určen Node (jeden nebo více), kde se zobrazují (například událost se zobrazí v nodu Root.Uživatelé.Uživatel.Jádro (jako Thread), 
        /// a současně v nodu Root.Uživatelé.Uživatel jako zobrazení délky procesu.
        /// Jiná událost se může zobrazovat v nodu Root.Uživatelé.Uživatel.Sql, a současně v nodu Root.SqlAktivita.
        /// </summary>
        public CollectIdx<ProcessInfo> Processes { get; private set; }
        /// <summary>
        /// Priorita času tohoto serveru. Implicitní = null, nelze určit.
        /// Tato property si automaticky střádá nejvyšší prioritu ze zadaných hodnot, stačí vždy zadat příkaz "server.ServerTimePriority = args.ServerTimePriority;"
        /// Priorita času řídí určování časových offsetů a určení řídícího serveru z hlediska přesného času.
        /// Nejvyšší prioritu 1 má mít SQL server.
        /// Prioritu 2 má mít aplikační server (pokud bude mít k dispozici data z SQL serveru, bude si řídit svoje hodinky podle něj).
        /// Prioritu 3 má mít klientský trace ("Server"), bude řídit svůj čas od prioritnějších míst.
        /// Priorita se nastavuje v TraceReaderu v metodě ITraceReader.CheckFileName() a/nebo ITraceReader.CheckContent().
        /// </summary>
        public Int32? ServerTimePriority
        {
            get { return this._ServerTimePriority; }
            set
            {
                if (value.HasValue && value.Value > 0 &&
                    (!this._ServerTimePriority.HasValue ||
                     (this._ServerTimePriority.HasValue && this._ServerTimePriority.Value > value.Value)))
                    this._ServerTimePriority = value;
            }
        }
        private Int32? _ServerTimePriority;
        /// <summary>
        /// Typ trace dat v tomto serveru. Jeden server může obsahovat nejvýše jeden typ dat.
        /// </summary>
        public string TraceDataType { get; private set; }
		/// <summary>
		/// Pořadí serveru podle Key, ale Descending (kvůli určení ElementGroup jednotlivých TraceInfo)
		/// </summary>
		public int ElementGroup { get; set; }
        /// <summary>
        /// Aktuální objekt ITraceReader, který právě nyní načítá data z trace souboru.
        /// Odkaz na tento objekt sem umísťuje instance třídy TraceInfo, při načítání dat. Po načtení dat je zde null.
        /// </summary>
        public ITraceReader CurrentTraceReader { get { return this.Main.CurrentTraceReader; } }
        /// <summary>
        /// Obsahuje aktuální Trace soubor, který se načítá
        /// </summary>
        public TraceInfo CurrentTraceInfo { get { return this.Main.CurrentTraceInfo; } }
        #endregion
        #region Property
        /// <summary>
        /// Aktuální počet procesů
        /// </summary>
        public int ProcessCount
        {
            get
            {
                return this.Processes.Count;
            }
        }
        /// <summary>
        /// Aktuální počet událostí (eventů)
        /// </summary>
        public int EventCount
        {
            get
            {
                int cnt = 0;
                foreach (ProcessInfo process in this.Processes)
                    cnt += process.Events.Count;
                return cnt;
            }
        }
        /// <summary>
        /// Režim zacházení s duplicitou při přidávání EventInfo do seznamu eventů procesu.
        /// Default = None: duplicity z principu zpracování nemůžou nastat, 
        /// nevyužívá se vkládání hotových eventů metodou args.ProcessEvent(), ale vkládají se Edge metodou args.ProcessItem().
        /// </summary>
        public DupliciteEventKeyHandlingType DupliciteEventKeyHandling { get; set; }
        #endregion
        #region Trace soubory tohoto serveru
        
        
        #endregion
        #region Přidávání a vyhledání dat, podpora pro Mergování
        /// <summary>
        /// Metoda přidá nový trace soubor.
        /// Trace soubor má být načten (hlavička) a připraven metodou this.DetectAndAddTrace().
        /// Trace soubor se přidává do Main (kde se plně eviduje) a do Server, a současně se přidává do nodu NodeTrace jako prvek Elements (aby za sebe generoval vizuální element).
        /// </summary>
        /// <param name="trace">Data o souboru trace</param>
        internal void AddTrace(TraceInfo trace, string traceDataType)
        {
            if (trace == null || String.IsNullOrEmpty(trace.Key)) return;

            // Přidat do kolekce (indexovaná ID i Key):
            this.Traces.Add(trace);
            this.TraceDataType = traceDataType;
        }
        /// <summary>
        /// Zajistí načtení dat (Content) z trace souborů pro požadovaný časový úsek.
        /// Načítá jen ty soubory, které dosud načteny nejsou.
        /// </summary>
        /// <param name="timeRange"></param>
        public void CheckLoadContentForTime(TimeRange timeRange)
        {
            bool needLoad = false;
            foreach (TraceInfo traceInfo in this.Traces)
            {
                if (traceInfo.Time.IsConjunct(timeRange) && !traceInfo.ContentProcessed)
                {
                    needLoad = true;
                    break;
                }
            }
            if (needLoad)
                this.Main.CheckLoadContentForTime(timeRange, this.TraceDataType);
        }
        /// <summary>
		/// Metoda přidá data načtená z trace souboru, data mají formu IEventEdge (=zlomek dat o události, začátek nebo konec).
		/// </summary>
		/// <param name="data"></param>
        internal void AddData(IEventEdge data)
		{
			ProcessInfo process = this.FindOrCreateProcess(data.ProcessKey);
			if (process != null)
				process.AddData(data);
		}
		/// <summary>
		/// Metoda přidá data načtená z trace souboru, data mají formu IEventItem (=celá zkompletovaná událost).
		/// </summary>
		/// <param name="data"></param>
        internal void AddData(IEventItem data)
		{
			ProcessInfo process = this.FindOrCreateProcess(data.ProcessKey);
			if (process != null)
				process.AddData(data);
		}
        /// <summary>
		/// Najde nebo vytvoří nový proces, a vrátí jej.
		/// Pokud na vstupu je prázdný string (null or empty), vrací null.
		/// </summary>
		/// <param name="processKey"></param>
		/// <returns></returns>
		public ProcessInfo FindOrCreateProcess(string processKey)
		{
			// Pokud není zadán název procesu, skončíme:
			if (processKey.IsEmpty()) return null;

			// Najdeme nebo vytvoříme nový proces:
			ProcessInfo process;
			if (!this.Processes.TryGetValue(processKey, out process))
			{
				process = new ProcessInfo(this, processKey);
				this.Processes.Add(process);
			}
			return process;
		}
		/// <summary>
		/// Metoda zajistí provedení dokončovacích operací po načtení obsahu trace souborů.
		/// Zajistí mergování (sloučení) událostí, které sloučit lze.
		/// Tuto metodu volá Main po dokončení načítání balíku Trace souborů (nemusí být načteny všechny, ale pro tuto chvíli je hotovo).
		/// </summary>
		internal void LoadContentFinalize()
		{
			foreach (ProcessInfo process in this.Processes)
				process.LoadContentFinalize();
		}
        /// <summary>
        /// Vymaže ze sebe všechna data událostí
        /// </summary>
        internal void MemoryFree()
        {
            foreach (ProcessInfo process in this.Processes)
                process.MemoryFree();
        }
        #endregion
        #region Řešení časového posunu eventu tohoto serveru proti vizuálnímu (reálnému) času. Pokud se řešení zdá někomu složité, je to tím že je autonomní, rychlé a robustní.
        /// <summary>
        /// Vrací vizuální čas pro daný časový úsek.
        /// Důvod je jednoduchý: různé servery mají různý čas, a s tímto časem zapisují události.
        /// V jednom trace souboru se pak setkávají záznamy o jedné události z více zdrojů, s různým časem.
        /// </summary>
        /// <param name="recordedTime">Čas, který je uveden v záznamech = čas serveru, nemusí být přesný</param>
        /// <returns></returns>
        public TimeRange GetVisualTime(TimeRange recordedTime)
        {
            if (this.TimeShiftDict.Count == 0) return recordedTime;       // Pokud nemám žádné informace o korekci času pro tento server, vracím vstupující čas.

            DateTime begin = GetVisualTime(recordedTime.Begin);
            DateTime end = GetVisualTime(recordedTime.End);

            return new TimeRange(begin, end);
        }
        /// <summary>
        /// Vrací vizuální čas pro daný časový úsek.
        /// Důvod je jednoduchý: různé servery mají různý čas, a s tímto časem zapisují události.
        /// V jednom trace souboru se pak setkávají záznamy o jedné události z více zdrojů, s různým časem.
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public DateTime GetVisualTime(DateTime dateTime)
        {
            if (this.TimeShiftDict.Count == 0) return dateTime;       // Pokud nemám žádné informace o korekci času pro tento server, vracím vstupující čas.
            if (dateTime == DateTime.MinValue) return dateTime;
            double offset = TimeShiftFind(dateTime);
            return dateTime.AddSeconds(offset);
        }
		/// <summary>
		/// Vrátí informaci o tom, zda server obsahuje jakoukoli informaci o časovém posunu.
		/// </summary>
		/// <param name="recordedTime"></param>
		/// <returns></returns>
        public bool ContainVisualOffset { get { return (this.TimeShiftDict.Count > 0); } }
		/// <summary>
        /// Vrátí informaci o tom, zda server již obsahuje infromaci o časovém posunu pro daný čas.
        /// Server si ukládá jednu informaci pro každých 15 minut. Pokud na serveru proběhne více událostí v témže čase, 
        /// pak časový offset mají všechny shodný (a není třeba určovat další a další).
        /// </summary>
        /// <param name="recordedTime"></param>
        /// <returns></returns>
        public bool ContainVisualOffsetForTime(DateTime recordedTime)
        {
            int key = TraceServerTimeOffset.TimeToKey(recordedTime);
            return this.TimeShiftDict.ContainsKey(key);
        }
        /// <summary>
        /// Metoda nastavuje reálný čas (který bude sloužit k zobrazování) pro daný čas záznamu.
        /// </summary>
        /// <param name="recordedTime">Čas, který je uveden v záznamech = čas serveru, nemusí být přesný</param>
        /// <param name="visualTime">Čas, který je v danou dobu reálný (určený v přesnějším zdroji).
        /// Na tomto času na časové ose se má zobrazovat událost, která je zaznamenaná s časem recordedTime.</param>
        public void SetVisualTime(DateTime recordedTime, DateTime visualTime)
        {
            // Určím čas a uložím data (založím nový / opravím stávající záznam):
            int key = TraceServerTimeOffset.TimeToKey(recordedTime);
            double offset = ((TimeSpan)(visualTime - recordedTime)).TotalSeconds;

            this.SetVisualTime(key, offset, true);
        }
        /// <summary>
        /// Metoda nastavuje reálný čas (který bude sloužit k zobrazování) pro daný čas záznamu.
        /// </summary>
        /// <param name="recordedTime">Čas, který je uveden v záznamech = čas serveru, nemusí být přesný</param>
        /// <param name="visualTime">Čas, který je v danou dobu reálný (určený v přesnějším zdroji).
        /// Na tomto času na časové ose se má zobrazovat událost, která je zaznamenaná s časem recordedTime.</param>
        public void SetVisualTime(int key, double offset)
        {
            this.SetVisualTime(key, offset, true);
        }
        /// <summary>
        /// Metoda nastavuje reálný čas (který bude sloužit k zobrazování) pro daný čas záznamu.
        /// </summary>
        /// <param name="recordedTime">Čas, který je uveden v záznamech = čas serveru, nemusí být přesný</param>
        /// <param name="visualTime">Čas, který je v danou dobu reálný (určený v přesnějším zdroji).
        /// Na tomto času na časové ose se má zobrazovat událost, která je zaznamenaná s časem recordedTime.</param>
        public void SetVisualTime(int key, double offset, bool saveToDocument)
        {
            // Smažu cache:
            this.ClearTimeShiftCache();

            TraceServerTimeOffset timeOffset;
            if (!this.TimeShiftDict.TryGetValue(key, out timeOffset))
            {
                timeOffset = new TraceServerTimeOffset(key, offset);
                this.TimeShiftDict.Add(key, timeOffset);
            }
            else
            {
                timeOffset.Offset = offset;
            }

            // Automaticky zajistím persistenci dat:
            if (saveToDocument)
                this.SaveToDocument();
        }
		/// <summary>
		/// Vymaže definice časových posunů pro tento server.
		/// </summary>
		internal void ClearTimeShifts()
		{
			this.TimeShiftDict = new Dictionary<int, TraceServerTimeOffset>();
			this.ClearTimeShiftCache();
			this.SaveToDocument();
		}
		/// <summary>
        /// Vymaže obsah vnitřní cache časových posunů.
		/// Nemaže trvalá data. Cache se vytvoří znovu v korektním stavu až bude potřeba.
        /// </summary>
        internal void ClearTimeShiftCache()
        {
            if (this.TimeShiftsCache != null) this.TimeShiftsCache = null;
            if (this.TimeShiftList != null) this.TimeShiftList = null;
        }
        /// <summary>
        /// Vrátí časový posun (v sekundách) pro daný okamžik.
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        protected double TimeShiftFind(DateTime time)
        {
            if (this.TimeShiftDict.Count == 0) return 0D;
            if (time == DateTime.MinValue) return 0D;

            this.PrepareTimeShiftCache();

            if (this.TimeShiftList.Count == 1) return this.TimeShiftList[0].Offset;   // Pokud máme data jen pro jednu hodinu, pak je to jednoduché - není co hledat a ani co interpolovat.

            // Pokud pro daný čas už mám data v cache (Dictionary), tak data rovnou vrátím:
            int key = TraceServerTimeOffset.TimeToKey(time);                          // Z daného data a času vrátí Int32 = klíč času
            double result = 0D;
            if (this.TimeShiftsCache.TryGetValue(key, out result)) return result;     // Pokud pro daný čas už mám určený ofset, vrátím jej IHNED !!!   (tady je ta rychlost)

            // Výchozí stav dat:
            //  1) seznam má přinejmenším jednu položku, takže najdeme přinejmenším jednu stranu (L nebo H)
            //  2) zadaný čas (key) není jednou z hodnot v seznamu (to bychom ji našli v Dictionary), takže bude někde mezi hodnotami a ne přesně v některé.
            // Najdu nejbližší záznamy pro hodinu nižší a pro hodinu vyšší, v poli TimeShiftList (které je setříděné časem, a průběžně se doplňuje spolu s doplňováním cache):
            int idxL = this.TimeShiftList.FindLastIndex(i => i.Key < key);
            int idxH = this.TimeShiftList.FindIndex(i => i.Key > key);

            // Tady jsou tři možnosti: hledaná hodina (hour) je buď menší než nejmenší dosud evidovaná hodina (pak idxL == -1), nebo mezi nimi, nebo za poslední:
            if (idxL < 0)
            {   // Hledaná hodina je pod minimální: offset převezmu z minimální:
                result = this.TimeShiftList[idxH].Offset;
                this.TimeShiftList.Insert(0, new TraceServerTimeOffset(key, result));
            }
            else if (idxL >= 0 && idxH >= 0)
            {   // Hledaná hodina je mezi minimální a maximální: offset určím interpolací:
                double offL = this.TimeShiftList[idxL].Offset;
                double keyL = (double)this.TimeShiftList[idxL].Key;
                double offH = this.TimeShiftList[idxH].Offset;
                double keyH = (double)this.TimeShiftList[idxH].Key;
                double keyD = (double)key - keyL;                          // Vzdálenost hledané hodiny od dolního údaje
                result = offL + ((offH - offL) * (keyD / (keyH - keyL)));  // Offset v dané hodině = offset dolní + poměrná část rozdílu offsetu (H-L) připadající na poměr času dané hodiny ku času H-L
                this.TimeShiftList.Insert(idxH, new TraceServerTimeOffset(key, result));
            }
            else
            {   // Hledaná hodina je nad maximální: offset převezmu z maximální:
                result = this.TimeShiftList[idxL].Offset;
                this.TimeShiftList.Add(new TraceServerTimeOffset(key, result));
            }

            // Máme výsledný offset, zapíšeme je do cache:
            this.TimeShiftsCache.Add(key, result);

            return result;
        }
        /// <summary>
        /// Pokud je to nutné, připraví k provozu cache pro časové posuny.
        /// </summary>
        protected void PrepareTimeShiftCache()
        {
            if (this.TimeShiftList == null)
            {
                this.TimeShiftList = new List<TraceServerTimeOffset>(this.TimeShiftDict.Values);
                this.TimeShiftList.Sort(CompareByKey);
            }
            if (this.TimeShiftsCache == null)
            {
                this.TimeShiftsCache = new Dictionary<int, double>();
                foreach (TraceServerTimeOffset timeOffset in this.TimeShiftDict.Values)
                    this.TimeShiftsCache.Add(timeOffset.Key, timeOffset.Offset);
            }
        }
        /// <summary>
        /// Komparátor podle časového klíče
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByKey(TraceServerTimeOffset a, TraceServerTimeOffset b)
        {
            if (a == null && b == null) return 0;
            if (a == null) return -1;
            if (b == null) return -1;
            return a.Key.CompareTo(b.Key);
        }
        /// <summary>
        /// Registr hodnot časových posunů = korekce času serveru.
        /// Toto je primární úložiště pro explicitně zadané hodnoty.
        /// Obsahuje časové posuny pro externě zadané hodiny.
        /// Pokud je seznam prázdný, pak je časový posun == 0.
        /// Pokud je v seznamu jediná hodnota, platí pro všechny časy.
        /// Jakmile je v seznamu více hodnot, pak se pro konkrétní čas dohledá nejbližší hodina.
        /// Konverzi času na hodinu řeší metoda TimeToHour(DateTime).
        /// Nalezení záznamu provádí metoda TimeShiftFind(DateTime).
        /// Záznamy se přidávají při závěrčné analýze po načtení sady souborů (viz metoda MaiInfo.LoadContentFinalize()).
        /// </summary>
        internal Dictionary<int, TraceServerTimeOffset> TimeShiftDict;
        /// <summary>
        /// Setříděné pole obsahující časové posuny.
        /// Slouží pro interpolaci hodnot pro jednotlivé hodiny, při prvním dotazu na danou hodinu.
        /// Najdou se nejbližší hodnoty pod a nad, a určí se interpolovaná hodnota, která se do odpovídající pozice vloží.
        /// Současně se hodnota vkládá i do TimeShiftsCache, takže příští dotaz bude vyřízen v minimálním okamžiku.
        /// </summary>
        internal List<TraceServerTimeOffset> TimeShiftList;
        /// <summary>
        /// Cache časových posunů pro konkrétní hodiny.
        /// Obsahuje explicitní i interpolované hodnoty.
        /// Klíčem je hodina (integer), určená jako počet hodin od času 2012-01-01 do daného času.
        /// Hodnotou je počet sekund (a milisekund) časového posunu platného pro tuto hodinu.
        /// </summary>
        protected Dictionary<int, double> TimeShiftsCache;
        #endregion
        #region Podpora pro persistenci dat časových offsetů
        /// <summary>
        /// Načte do sebe data uložená v trace dokumentu
        /// </summary>
        public void LoadFromDocument()
        {
            this.Document.LoadServerInfo(this);
        }
        /// <summary>
        /// Uloží ze sebe data do trace dokumentu
        /// </summary>
        public void SaveToDocument()
        {
            this.Document.SaveServerInfo(this);
        }
        /// <summary>
        /// Soupis časových offsetů tohoto serveru.
        /// Get = vytvoří jej (nikdy nevrací null);
        /// Set = akceptuje jej do sebe;
        /// </summary>
        public TraceServerInfoData ServerTimeOffsetData
        {
            get { return this._GetServerTimeOffsetData(); }
            set { this._SetServerTimeOffsetData(value); }
        }

        private TraceServerInfoData _GetServerTimeOffsetData()
        {
            TraceServerInfoData result = new TraceServerInfoData(this.ServerName);
            foreach (TraceServerTimeOffset timeOffset in this.TimeShiftDict.Values)
            {
                result.TimeOffsetList.Add(new TraceServerTimeOffset(timeOffset.Key, timeOffset.Offset));
            }
            return result;
        }

        private void _SetServerTimeOffsetData(TraceServerInfoData value)
        {
            this.ClearTimeShiftCache();
            this.TimeShiftDict = new Dictionary<int, TraceServerTimeOffset>();
            if (value != null && value.TimeOffsetList != null && value.TimeOffsetList.Count > 0)
            {
                foreach (TraceServerTimeOffset timeOffset in value.TimeOffsetList)
                {
                    if (!this.TimeShiftDict.ContainsKey(timeOffset.Key))
                        this.TimeShiftDict.Add(timeOffset.Key, timeOffset);
                }
            }
        }
        #endregion
        #region Vytváření grafických prvků (barva elementů pro elementy za Trace)
        /// <summary>
        /// Barva určená pro pozadí elementů tohoto serveru.
        /// Elementy jednoho serveru se kreslí do společné řádky ("trace soubory"), ale každý do jiné skupiny (elementGroup) a jinou barvou, aby vizuálně následovaly.
        /// </summary>
        internal Color ElementBackColor { get { return this.Main.GetColor("Server", this.Id); } }
        #endregion
		#region IPlugin, IColorDeclarator Members : deklarace konkrétních barev pro názvy ColorGroups
		bool IPlugin.Active { get { return true; } }
		void IColorDeclarator.ColorDeclare(ColorDeclaratorArgs args)
		{
			args.Append("Server", Color.Khaki);
		}
		// Aby třída mohla být Pluginem, musí mít bezparametrický konstruktor:
		private ServerInfo() : base() { }
		#endregion
    }
    #endregion
    #region ProcessInfo : data jednoho procesu, spuštěného v rámci serveru. Obsahuje data z více trace, ale navzájem související. Zde vznikají EventInfo.
    /// <summary>
    /// ProcessInfo : data jednoho procesu, spuštěného v rámci serveru. Obsahuje data z více trace, ale navzájem související. Typicky jedna session.
	/// </summary>
    public class ProcessInfo : BaseInfo, IDataElement, IColorDeclarator
    {
        #region Konstrukce a základní property
        internal ProcessInfo(ServerInfo serverInfo, string process)
			: base(serverInfo.Main, process)
        {
            this.Server = serverInfo;
			this.Events = new CollectIdx<EventInfo>();
			this.UnresolvedEvents = new Dictionary<GID, EventInfo>();
			this.Merges = new CollectIdx<MergeInfo>();
			this.Time = TimeRange.Empty;
        }
        public override void Dispose()
        {
            base.Dispose();

            this.Server = null;

            if (this.Events != null)
            {
                foreach (EventInfo eventInfo in this.Events)
                    eventInfo.Dispose();
                this.Events.Dispose();
            }

            if (this.UnresolvedEvents != null)
                this.UnresolvedEvents.Clear();

            if (this.Merges != null)
            {
                foreach (MergeInfo mergeInfo in this.Merges)
                    mergeInfo.Dispose();
                this.Merges.Dispose();
            }
        }
        public override string ToString()
        {
            TimeRange time = this.Time;
            return "Process=" + this.Key + "; Time=" + time.StringBeginAndSecs + "; ThingCount=" + this.Events.Count.ToString();
        }
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public override int ClassNr { get { return Constants.ClassNumberProcess; } }
        /// <summary>
        /// Server, na němž tento proces běžel
        /// </summary>
        public ServerInfo Server { get; private set; }
        /// <summary>
		/// Události, k nimž došlo v tomto procesu.
        /// Id = ID události (integer), hodnota je do události přidělená systémem, zcela unikátní pro každou událost přes celý systém.
        /// Key = Key události = string, kterým tuto událost identifikuje DataReader (načteno z trace souboru). 
		/// Může být null nebo empty. Pak podle této hodnoty nelze dohledat.
		/// Pokud je zadané, pak se položky typu IEventEdge dohledávají a přidávají ke stávajícím položkám dovnitř.
        /// </summary>
        public CollectIdx<EventInfo> Events { get; private set; }
        /// <summary>
        /// Seznam událostí, které dosud nebyly zařazeny do odpovídajících nodů, protože dosud nebyl známý HomeNode
        /// </summary>
        public Dictionary<GID, EventInfo> UnresolvedEvents { get; private set; }
        /// <summary>
		/// Bloky událostí, které jsou mergovatelné.
		/// Každý blok (MergeInfo) obsahuje několik událostí, které mají shodnou hodnotu MergeKey.
		/// Uvnitř tohoto bloku dochází fyzicky k mergování (slučování).
        /// </summary>
		public CollectIdx<MergeInfo> Merges { get; private set; }
        /// <summary>
        /// Reference na relativní node, v němž se mohou zobrazovat data (události) tohoto procesu, které mají target definován relativně.
        /// Typicky jde o node Root.Uživatelé.Uživatel. Node je určen v příchozích datech (IEvent
        /// Do tohoto nodu se (po jeho definici) vepíše element za celý tento proces, který představuje jeho čas a další informace o něm.
        /// Pokud do procesu vstupují události, které se mají zobrazovat v relativním nodu, a ten přitom není určen, 
		/// pak události čekají ve frontě na čas, kdy bude relativní node určen, a pak se do něj vepíšou.
        /// Pokud událost vstoupí do procesu v době, kdy je relativní node znám, pak se do něj vepisují ihned.
        /// Pokud je v události uveden absolutní node, pak ve frontě nečeká a do daného nodu se vepíše ihned.
        /// </summary>
        public NodeInfo HomeNode { get; private set; }
        /// <summary>
        /// true, pokud existuje HomeNode
        /// </summary>
        public bool HomeNodeExists { get { return this.HomeNode != null; } }
        /// <summary>
        /// Komparátor dvou procesů podle data jejich počátku.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static int CompareByBegin(ProcessInfo a, ProcessInfo b)
        {
            return a.Time.Begin.CompareTo(b.Time.Begin);
        }
        /// <summary>
        /// Celkový čas.
        /// Jde o součet časů ze všech událostí.
        /// Udržuje se průběžně, anebo hromadným přepočtem.
        /// </summary>
        public TimeRange Time { get; protected set; }
        /// <summary>
        /// Aktuální objekt ITraceReader, který právě nyní načítá data z trace souboru.
        /// Odkaz na tento objekt sem umísťuje instance třídy TraceInfo, při načítání dat. Po načtení dat je zde null.
        /// </summary>
        public ITraceReader CurrentTraceReader { get { return this.Main.CurrentTraceReader; } }
        /// <summary>
        /// Obsahuje aktuální Trace soubor, který se načítá
        /// </summary>
        public TraceInfo CurrentTraceInfo { get { return this.Main.CurrentTraceInfo; } }
        #endregion
		#region Přidávání a vyhledání dat, podpora pro Mergování, invalidace nodů po změně obsahu elementu
		/// <summary>
		/// Zkusí najít událost podle jejího klíče Key.
		/// Pokud neexistuje, vrací null (nezakládá novou událost).
		/// </summary>
		/// <param name="eventKey"></param>
		/// <returns></returns>
		public EventInfo TryFindEvent(string eventKey)
		{
			EventInfo eventInfo;
			if (!eventKey.IsEmpty() && this.Events.TryGetValue(eventKey, out eventInfo))
				return eventInfo;
			return null;
		}
		/// <summary>
		/// Metoda přidá data načtená z trace souboru, data mají formu IEventEdge (=zlomek dat o události, začátek nebo konec).
		/// </summary>
		/// <param name="data"></param>
		internal void AddData(IEventEdge data)
		{
			// Pokud je v eventu uveden EventKey, zkusíme jej najít, jinak jej založíme:
            EventInfo eventInfo = null;
            if (!data.EventKey.IsEmpty() && this.Events.TryGetValue(data.EventKey, out eventInfo))
            {   // Událost máme, vložíme do ní došlá data:
                eventInfo.AddData(data, this.CurrentTraceInfo);
                this.ReMergeEvent(eventInfo);
				this.StoreEventIntoNodes(eventInfo);
				this.InvalidateNodeWithEvent(eventInfo);
            }
            else
            {   // Událost nemáme, vytvoříme novou, vložíme do ní data a pak ji vložíme do zdejších struktur:
                eventInfo = this.CreateEventItem(data);

                // Událost vložíme do těch nodů, které sama deklaruje jako svoje vhodné cíle 
                //  (před tím ověří, zda by nebylo možno případné starší události čekající na určené HomeNode vyřešit):
                // Anebo pokud dosud není znám HomeNode, pak událost zařadí do fronty nevyřešených událostí.
				if (eventInfo != null)
					this.StoreEventIntoNodes(eventInfo);
            }
			this.TimeSummarize(eventInfo);
		}
		/// <summary>
		/// Metoda přidá data načtená z trace souboru, data mají formu IEventItem (=celá zkompletovaná událost).
		/// </summary>
		/// <param name="data"></param>
        internal void AddData(IEventItem eventItem)
		{
            // Vytvořit EventInfo (tady není zapotřebí datový zdroj):
            if (eventItem == null) return;
            EventInfo eventInfo = new EventInfo(this, eventItem);

            // Zařadit EventInfo do this struktur:
            bool addEvent = this.AddEventInfo(eventInfo);

			// Událost vložíme do těch nodů, které sama deklaruje jako svoje vhodné cíle 
			//  (před tím ověří, zda by nebylo možno případné starší události čekající na určené HomeNode vyřešit):
			// Anebo pokud dosud není znám HomeNode, pak událost zařadí do fronty nevyřešených událostí.
            if (addEvent && eventInfo != null)
				this.StoreEventIntoNodes(eventInfo);
		}
        /// <summary>
        /// Metoda sestaví objekt EventInfo (zdejší obálka nad daty IEventItem) na základě vstupních dat IEventEdge (jeden fragment události),
        /// do vytvořeného objektu vloží první data (z parametru IEventEdge data),
        /// a tento objekt zařadí do zdejších struktur this.Events a this.Merges.
        /// Neřeší umístění do Nodů.
        /// Objekt IEventItem (obecný popis události) sestaví s využitím datového zdroje.
        /// Datový zdroj může událost ignorovat a z metody ITraceReader.CreateIEventItem() namísto objektu IEventItem vrátí hodnotu null, pak i tato metoda vrací null.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        protected EventInfo CreateEventItem(IEventEdge data)
        {
            // Vytvořit IEventItem a zabalit ho do EventInfo:
            IEventItem eventItem = this.CurrentTraceReader.CreateIEventItem(data);
            if (eventItem == null) return null;
            EventInfo eventInfo = new EventInfo(this, eventItem);

            // Vepsat do události data, teprve potom ji vložím do struktur:
            eventInfo.AddData(data, this.CurrentTraceInfo);

            // Zařadit EventInfo do this struktur:
            bool addEvent = this.AddEventInfo(eventInfo);

            return (addEvent ? eventInfo : null);
        }
        /// <summary>
        /// Metoda zařadí dodanou událost do zdejších struktur.
        /// Nezařazuje ji do nodů.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected bool AddEventInfo(EventInfo eventInfo)
        {
            bool addEvent = false;
            if (eventInfo == null)
                return addEvent;

            switch (this.Server.DupliciteEventKeyHandling)
            {
                case DupliciteEventKeyHandlingType.None:
                    addEvent = true;
                    break;
                case DupliciteEventKeyHandlingType.ExplicitCheck:
                    addEvent = !this.Events.ContainsKey(eventInfo.Key);
                    if (!addEvent)
                    {
                        this.DocumentAuditlog.AddAuditItem("Duplicitní klíč EventKey",
                            new MasterDetailItem("Soubor", CurrentTraceInfo.FileDirName),
                            new MasterDetailItem("Řádek", eventInfo.LineNumber),
                            new MasterDetailItem("Duplicitní klíč", eventInfo.Key));
                        throw new System.ArgumentException("Duplicitní klíč EventKey = " + eventInfo.Key + " v souboru " + CurrentTraceInfo.FileDirName + " na řádku " + eventInfo.LineNumber.ToString());
                    }
                    break;
                case DupliciteEventKeyHandlingType.AuditAndSkip:
                    addEvent = !this.Events.ContainsKey(eventInfo.Key);
                    if (!addEvent)
                    {
                        this.DocumentAuditlog.AddAuditItem("Duplicitní klíč EventKey",
                            new MasterDetailItem("Soubor", CurrentTraceInfo.FileDirName),
                            new MasterDetailItem("Řádek", eventInfo.LineNumber),
                            new MasterDetailItem("Duplicitní klíč", eventInfo.Key));
                    }
                    break;
                case DupliciteEventKeyHandlingType.SilentSkip:
                    addEvent = !this.Events.ContainsKey(eventInfo.Key);
                    break;
            }
            if (!addEvent)
                return addEvent;

            this.Events.Add(eventInfo);

            if (!eventInfo.MergeKey.IsEmpty())
            {
                MergeInfo mergeInfo = this.FindOrCreateMerge(eventInfo.MergeKey);
                mergeInfo.AddEventInfo(eventInfo);
            }
            return addEvent;
        }
        /// <summary>
        /// Metoda řeší problém, kdy nějaký event je součástí Mergování, již byl přimergován k jinému elementu a byl odebrán z fronty Merge,
        /// a nyní do něj byla vepsána nová data (metodou eventInfo.AddData(IEventEdge data, this.CurrentTraceInfo)).
        /// V takovém případě je třeba, aby se tento event znovu dostal do fronty Merge, aby jeho nová data (typicky čas End) se promítnul do výsledného elementu.
        /// Konkrétně tedy: pokud tento event má MergeKey (má se mergovat), a pokud jeho MergedIntoEventId != 0 (už byl někam jinam přimergován), pak jej znovu přidáme do fronty Merge.
        /// Příští chod Mergování jej najde, data z něj načte a objekt z fronty opět odebere.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected void ReMergeEvent(EventInfo eventInfo)
        {
            if (!eventInfo.MergeKey.IsEmpty() && eventInfo.MergedIntoEventId != 0)
            {
                MergeInfo mergeInfo = this.FindOrCreateMerge(eventInfo.MergeKey);
                if (!mergeInfo.ContainsEventInfo(eventInfo))
                    mergeInfo.AddEventInfo(eventInfo);
            }
        }
        /// <summary>
        /// Najde nebo vytvoří a vrátí MergeInfo pro daný klíč = objekt, do něhož je možno umisťovat eventy ke spojování, a který toto spojování poté dokáže zajistit.
        /// </summary>
        /// <param name="mergeKey"></param>
        /// <returns></returns>
        public MergeInfo FindOrCreateMerge(string mergeKey)
        {
            if (mergeKey.IsEmpty()) return null;
            MergeInfo mergeInfo;
            if (!this.Merges.TryGetValue(mergeKey, out mergeInfo))
            {
                mergeInfo = new MergeInfo(this, mergeKey);
                this.Merges.Add(mergeInfo);
            }
            return mergeInfo;
        }
        /// <summary>
        /// Vrátí souhrn událostí (IEventItem), které jsou nyní načteny v tomto procesu, a které vyhovují zadanému filtru (predicate).
        /// </summary>
        /// <param name="predicate">Filtr na jednotlivé události (predikát)</param>
        /// <returns></returns>
        public List<IEventItem> GetLoadedEvents(Func<IEventItem, bool> predicate)
        {
            List<IEventItem> result = new List<IEventItem>();
            foreach (EventInfo eventInfo  in this.Events)
            {
                IEventItem item = eventInfo.EventItem;
                if (predicate == null || predicate(item))
                    result.Add(item);
            }
            return result;
        }
        /// <summary>
		/// Metoda zajistí provedení dokončovacích operací po načtení obsahu trace souborů.
		/// Zajistí mergování (sloučení) událostí, které sloučit lze.
		/// </summary>
		internal void LoadContentFinalize()
		{
			// Proces je reprezentován jako element v prostoru navázaného uživatele:
			this.UpdateProcessElement();

			// Mergování:
			foreach (MergeInfo mergeInfo in this.Merges)
				mergeInfo.MergeEvents();
		}
        /// <summary>
        /// Vymaže ze sebe všechna data událostí
        /// </summary>
        internal void MemoryFree()
        {
            foreach (EventInfo eventInfo in this.Events)
                eventInfo.Dispose();
            this.Events.Clear();

            this.Merges.Clear();
        }
        /// <summary>
		/// Metoda aktualizuje svůj element ve svém HomeNode, pokud jej má.
		/// </summary>
		protected void UpdateProcessElement()
		{
			if (this.HomeNode != null)
			{
				if (!this.HomeNode.ContainElement(this.GId))
					this.HomeNode.AddElement(this);
				else if (this.Time != this.LastElementTime)
					this.HomeNode.InvalidateElements();
			}
		}
		/// <summary>
		/// Sumarizuje čas procesu na základě dodané události
		/// </summary>
		/// <param name="eventInfo"></param>
		protected void TimeSummarize(EventInfo eventInfo)
		{
			if (eventInfo == null) return;
			DateTime dMin = DateTime.MinValue;
			DateTime pBeg = this.Time.Begin;
			DateTime pEnd = this.Time.End;
			DateTime eBeg = eventInfo.Time.Begin;
			DateTime eEnd = eventInfo.Time.End;
			bool pb = (pBeg > dMin);
			bool pe = (pEnd > dMin);
			bool eb = (eBeg > dMin);
			bool ee = (eEnd > dMin);
			if ((!pb && eb) || (pb && eb && eBeg < pBeg))
				this.Time = new TimeRange(eBeg, this.Time.End);
			if ((!pe && ee) || (pe && ee && eEnd > pEnd))
				this.Time = new TimeRange(this.Time.Begin, eEnd);
		}
		/// <summary>
		/// Čas celého tohoto procesu, který se naposledy potvrdil do nodu this.HomeNode
		/// </summary>
		internal TimeRange LastElementTime { get; private set; }
        #endregion
        #region Vkládání událostí do odpovídajících nodů, tvorba nodů, jejich invalidace
        /// <summary>
        /// Uloží danou událost do těch nodů, o kterých událost sama tvrdí, že do nich patří.
        /// Pokud událost obsahuje byť i jediný cílový node, který je relativní, a v současné době není korektně určen základní node pro relativní cesty (this.HomeNode je null),
        /// pak se tato událost přidává do soupisu čekajících událostí this.UnresolvedEvents.
        /// Jakmile v některém příštím eventu (který přijde do této metody) bude určen relativní node, pak dojde k jeho vyhledání / založení, a prioritně se do něj vepíšou ty čekající eventy,
        /// a teprve po nich se vyřeší node vstupující.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected void StoreEventIntoNodes(EventInfo eventInfo)
        {
            this.CheckUnresolvedEvents(eventInfo);

            // Pokud HomeNode existuje, anebo pokud aktuální event nepotřebuje HomeNode, pak vložím událost do všech nodů, o které událost žádá.
            // Pokud neexistuje, pak si celou událost založím do soupisu nevyřešených událostí.
			if (this.HomeNodeExists || !eventInfo.TargetHomeNodeRequired)
				this.StoreEventIntoNodesSolved(eventInfo);
			else
			{	// Event patří do nějakého HomeNode, ale nevíme do kterého.
				// Zařadíme si event do soupisu čekajících eventů (jen 1x, ne duplicitně):
				if (!this.UnresolvedEvents.ContainsKey(eventInfo.GId))
					this.UnresolvedEvents.Add(eventInfo.GId, eventInfo);
			}
        }
		/// <summary>
		/// Odeberu daný event ze všech jeho nodes
		/// </summary>
		/// <param name="eventInfo"></param>
		internal void RemoveEventFromNodes(EventInfo eventInfo)
		{
			// Pokud dosud není určen HomeNode, pak nemusím nody invalidovat (náš event v nich dosud nemůže být):
			if (!this.HomeNodeExists) return;

			// Pokud event nemá určené cílové nody, není koho invalidovat:
			if (eventInfo.TargetPaths == null || eventInfo.TargetPaths.Length == 0) return;
			foreach (string path in eventInfo.TargetPaths)
			{
				NodeInfo node = this.FindOrCreateNode(path, eventInfo);          // najde nebo za pomoci eventu (metody ITraceReader.CreateFullTreePath()) vytvoří node včetně všech vlastností
				if (node != null)
					node.RemoveElement(eventInfo.GId);
			}
		}
		/// <summary>
		/// Zajistí invalidaci všech nodů (z hlediska obsažených elementů), v nichž se vyskytuje daný event.
		/// Provádí se po změně obsahu tohoto eventu.
		/// </summary>
		/// <param name="eventInfo"></param>
		internal void InvalidateNodeWithEvent(EventInfo eventInfo)
		{
			// Pokud dosud není určen HomeNode, pak nemusím nody invalidovat (náš event v nich dosud nemůže být):
			if (!this.HomeNodeExists) return;

			// Pokud event nemá určené cílové nody, není koho invalidovat:
			if (eventInfo.TargetPaths == null || eventInfo.TargetPaths.Length == 0) return;
			foreach (string path in eventInfo.TargetPaths)
			{
				NodeInfo node = this.FindOrCreateNode(path, eventInfo);          // najde nebo za pomoci eventu (metody ITraceReader.CreateFullTreePath()) vytvoří node včetně všech vlastností
				if (node != null)
					node.InvalidateElements();
			}
		}
        /// <summary>
        /// Vloží tuto událost do těch nodů, do kterých patří.
        /// HomeNode nyní existuje.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected void StoreEventIntoNodesSolved(EventInfo eventInfo)
        {
			// Podle TreePath
			if (eventInfo.TreePaths != null && eventInfo.TreePaths.Length > 0)
			{
				foreach (TreePath treePath in eventInfo.TreePaths)
				{
					NodeInfo node = this.FindOrCreateNode(treePath, eventInfo);     // Najde, nebo za pomoci eventu (metody ITraceReader.CreateFullTreePath()) vytvoří node včetně všech vlastností
					if (node != null)
						node.AddElement(eventInfo);
				}
			}

			// Podle stringů:
			if (eventInfo.TargetPaths != null && eventInfo.TargetPaths.Length > 0)
			{
				foreach (string path in eventInfo.TargetPaths)
				{
					NodeInfo node = this.FindOrCreateNode(path, eventInfo);          // Najde, nebo za pomoci eventu (metody ITraceReader.CreateFullTreePath()) vytvoří node včetně všech vlastností
					if (node != null)
						node.AddElement(eventInfo);
				}
			}
        }
        /// <summary>
        /// Metoda zjistí, zda v současnosti čekají nějaké nody na vyřešení jejich HomeNode, a zda by nebylo možno na základě dat z dodaného eventu určit HomeNode a vyřešit je.
        /// Dodaný node nikam nevkládá.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected void CheckUnresolvedEvents(EventInfo eventInfo)
        {
            // Pokud dosud není určen HomeNode, a přitom v současné vstupní události je specifikován, pak tento node vytvoříme:
            if (!this.HomeNodeExists && !eventInfo.TargetHomeNode.IsEmpty())
                this.CreateHomeNode(eventInfo);

            // Pokud stále nemám založen HomeNode, skončím protože není jak události zařadit:
            // Anebo pokud není co řešit:
            if (this.HomeNode == null || this.UnresolvedEvents.Count == 0)
                return;

            // Máme relativní node, a máme i eventy k vyřešení => pustéme se do toho ("to" je: zařadíme eventy do nodů, o kterých tvrdí, že by tam rádi byli):
            foreach (EventInfo solveEvent in this.UnresolvedEvents.Values)
                this.StoreEventIntoNodesSolved(solveEvent);

            this.UnresolvedEvents.Clear();
        }
        /// <summary>
        /// Najde / založí a vrátí node, který je uveden jako eventInfo.TargetHomeNode.
        /// </summary>
        /// <param name="eventInfo"></param>
        protected void CreateHomeNode(EventInfo eventInfo)
        {
            string path = eventInfo.TargetHomeNode;
            if (!path.IsEmpty())
            {
                this.HomeNode = null;                // To tu musí být. Pokud by this.HomeNode byl not null, a cesta a¨path by byla relativní, počítala by se od tohoto this.HomeNode.
                this.HomeNode = this.FindOrCreateNode(path, eventInfo);
            }
        }
        /// <summary>
        /// Metoda prověří existenci nodu zadaného stringovou cestou.
        /// Pokud je zadaná cesta relativní (nezačíná zpětným lomítkem), zahajuje se hledání nodem this.HomeNode. Pokud by byl null, vrátí se null.
        /// Pokud node neexistuje, založí jej - k tomu účelu využije metodu ITraceReader.CreateFullTreePath(), která na základě stringové cesty vygeneruje plnou TreePath (včetně definice textů a chování a vzhledu řádků).
        /// Vrací node (nebo null, když je na vstuu empty).
        /// </summary>
        /// <param name="path"></param>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        protected NodeInfo FindOrCreateNode(string path, EventInfo eventInfo)
        {
            // Základní node: pokud je cesta zadaná jako absolutní, pak Main.NodeRoot, jinak zdejší HomeNode.
            NodeInfo baseNode = (TreePath.IsAbsolute(path) ? this.Main.NodeRoot : this.HomeNode);
            if (baseNode == null) return null;

            // Zkusím cestu najít (a nevytvářet nový node), pokud jej najdu, vrátím jej:
            NodeInfo findNode = baseNode.FindSubNode(path);
            if (findNode != null) return findNode;

            // Budu subnode vytvářet. Nějaká část Tree dosud neexistuje.
            TreePath treePath = CreateFullPathForEvent(path, eventInfo);
            if (treePath != null)
                findNode = baseNode.AddPath(treePath, this.Main.CurrentTraceType);

            return findNode;
        }
        /// <summary>
        /// Metoda prověří existenci nodu zadaného stringovou cestou.
        /// Pokud je zadaná cesta relativní (nezačíná zpětným lomítkem), zahajuje se hledání nodem this.HomeNode. Pokud by byl null, vrátí se null.
        /// Pokud node neexistuje, založí jej - k tomu účelu využije metodu ITraceReader.CreateFullTreePath(), která na základě stringové cesty vygeneruje plnou TreePath (včetně definice textů a chování a vzhledu řádků).
        /// Vrací node (nebo null, když je na vstuu empty).
        /// </summary>
		/// <param name="treePath"></param>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
		protected NodeInfo FindOrCreateNode(TreePath treePath, EventInfo eventInfo)
        {
            // Základní node: pokud je cesta zadaná jako absolutní, pak Main.NodeRoot, jinak zdejší HomeNode.
			NodeInfo baseNode = (treePath.AbsolutePath ? this.Main.NodeRoot : this.HomeNode);
            if (baseNode == null) return null;

            // Zkusím cestu najít (a nevytvářet nový node), pokud jej najdu, vrátím jej:
			NodeInfo findNode = baseNode.FindSubNode(treePath);
            if (findNode != null) return findNode;

            // Budu subnode vytvářet. Nějaká část Tree dosud neexistuje.
            findNode = baseNode.AddPath(treePath, this.Main.CurrentTraceType);

            return findNode;
        }
        /// <summary>
        /// Pro danou událost vytvoří plnou cestu k nodu.
        /// Využívá k tomu metodu datového zdroje ITraceReader.CreateFullTreePath().
        /// </summary>
        /// <param name="eventInfo"></param>
        /// <returns></returns>
        protected TreePath CreateFullPathForEvent(string path, EventInfo eventInfo)
        {
            TraceReaderCreatePathArgs args = new TraceReaderCreatePathArgs(this, path, eventInfo.EventItem);
            this.CurrentTraceReader.CreateFullTreePath(args);
            return args.TreePath;
        }
        #endregion
        #region Explicitní implementace interface IDataElement = objekt Process může hrát roli vizuálního elementu
        bool IDataElement.ElementVisible { get { return (this.Time.Seconds > 0D); } }
        GID IDataElement.GId { get { return this.GId; } }
        TimeRange IDataElement.TimeRange { get { return this.Time; } set { } }
		float IDataElement.ElementHeightRatio { get { return 0f; } }
		bool IDataElement.BeginIsUncertain { get { return false; } }
        bool IDataElement.EndIsUncertain { get { return false; } }
        int IDataElement.SubTimeSort { get { return 0; } }
        bool IDataElement.FullRow { get { return false; } }
        bool IDataElement.HideZeroWidthElementRectangle { get { return false; } }
        float[] IDataElement.Fragments { get { return null; } }
        GraphLayerType IDataElement.ElementLayer { get { return GraphLayerType.ElementItemLayer; } }
        int IDataElement.ElementGroup { get { return 0; } }
        GraphElementShapeType IDataElement.ElementShapeType { get { return GraphElementShapeType.Rectangle; } }
        ElementProperties IDataElement.Properties { get { return ElementProperties.None; } }
        ElementActionType IDataElement.ActionOnDoubleClick { get { return ElementActionType.DefaultByRow; } }
        GraphElementHandler IDataElement.DoubleClick { get { return null; } }
		string IDataElement.ColorGroup { get { return _COLOR_GROUP; } }
		Color IDataElement.BackColor { get { return this.Main.GetColor(_COLOR_GROUP, this.Id); } set { } }
        Color IDataElement.FontColor { get { return Color.Empty; } }
        bool IDataElement.CaptionShowAllways { get { return true; } }
        Image IDataElement.ElementImage { get { return null; } }
        ContentAlignment IDataElement.ImagePivotElementPoint { get { return ContentAlignment.MiddleCenter; } }
        ContentAlignment IDataElement.ImagePivotImagePoint { get { return ContentAlignment.MiddleCenter; } }
        string IDataElement.ToolTipInfo { get { return null; } }
        string[] IDataElement.Captions { get { return CreateCaptions(); } }

        string IDataElement.GetToolTipInfo(IDataRow rowData) { return null; }
        string IDataElement.GetCaption(IDataRow rowData, float charCount) { return null; }
        void IDataElement.DrawElement(GraphElementPainterArgs args) { }
		private const string _COLOR_GROUP = "Process";
        protected string[] CreateCaptions()
        {
            return new string[] 
            {
                "SessionId: " + this.Key,
                "SessionId: " + this.Key + "; Time: " + this.Time.StringFullDetail
            };
        }
        #endregion
		#region IPlugin, IColorDeclarator Members : deklarace konkrétních barev pro názvy ColorGroups
		bool IPlugin.Active { get { return true; } }
		void IColorDeclarator.ColorDeclare(ColorDeclaratorArgs args)
		{
			args.Append("Process", Color.Bisque);
		}
		// Aby třída mohla být Pluginem, musí mít bezparametrický konstruktor:
		private ProcessInfo() : base() { }
		#endregion
	}
    #endregion
	#region MergeInfo : seznam událostí jednoho procesu, které mají shodný MergeKey, které tedy lze mergovat.
	/// <summary>
	/// MergeInfo : seznam událostí jednoho procesu, které mají shodný MergeKey, které tedy lze mergovat.
	/// Klíčem (Key) je MergeKey.
	/// </summary>
    public class MergeInfo : BaseInfo
	{
		#region Konstrukce a základní property
		internal MergeInfo(ProcessInfo processInfo, string mergeKey)
			: base(processInfo.Main, mergeKey)
        {
			this.Process = processInfo;
            this.Events = new List<EventInfo>();
        }
        public override string ToString()
        {
            return "Merge=" + this.Key + "; EventCount=" + this.Events.Count.ToString();
        }
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public override int ClassNr { get { return Constants.ClassNumberMerge; } }
		/// <summary>
		/// Proces, v němž tyto události leží
		/// </summary>
		public ProcessInfo Process { get; private set; }
		/// <summary>
		/// Události, které je možno mezi sebou mergovat.
		/// Jde o prostý List, bude se třídit, nebude se v něm hledat podle indexu.
		/// </summary>
        public List<EventInfo> Events { get; private set; }
        /// <summary>
        /// Aktuální objekt ITraceReader, který právě nyní načítá data z trace souboru.
        /// Odkaz na tento objekt sem umísťuje instance třídy TraceInfo, při načítání dat. Po načtení dat je zde null.
        /// </summary>
        public ITraceReader CurrentTraceReader { get { return this.Main.CurrentTraceReader; } }
        /// <summary>
        /// Obsahuje aktuální Trace soubor, který se načítá
        /// </summary>
        public TraceInfo CurrentTraceInfo { get { return this.Main.CurrentTraceInfo; } }
        #endregion
        #region Přidávání a vyhledání dat, podpora pro Mergování
        /// <summary>
        /// Vrátí true, pokud this obsahuje event shodné hodnoty Id jakou má daný event.
        /// </summary>
        /// <param name="eventInfo"></param>
        internal bool ContainsEventInfo(EventInfo eventInfo)
        {
            return (this.Events.Exists(ev => ev.Id == eventInfo.Id));
        }
        /// <summary>
        /// Metoda přidá další událost do svého soupisu.
        /// </summary>
        /// <param name="eventInfo"></param>
        internal void AddEventInfo(EventInfo eventInfo)
        {
            this.Events.Add(eventInfo);
        }
		/// <summary>
		/// Metoda provede pokus o mergování (sloučení) událostí, které sloučit lze.
		/// </summary>
		internal void MergeEvents()
		{	// Teprve zde, na této úrovni (MergeInfo) dojde k vlastnímu slučování událostí. 
			// Zde jsou separované události, které mergovat lze.
			int cnt = this.Events.Count;
			if (cnt <= 1) return;
			this.Events.Sort(EventInfo.CompareByBegin);

			EventInfo prev = this.Events[0];
			for (int e = 1; e < cnt; e++)
			{
				EventInfo next = this.Events[e]; ;
				if (prev.MergeWith(next))
				{
					next.MergedIntoEventId = prev.Id;

					// Invalidace nodu Prev, odebrání eventu Next ze všech nodů:
					this.Process.InvalidateNodeWithEvent(prev);
					this.Process.RemoveEventFromNodes(next);

					// Next odeberu z this (MergeInfo), je hotový:
					this.Events.RemoveAt(e);
					e--;
					cnt--;
				}
				else
				{
					prev = next;
				}
			}
		}
        #endregion
    }
	#endregion
	#region EventInfo : obálka nad daty IEventItem. S tímto objektem se v datech pracuje, tento objekt v sobě hostuje IEventItem a zprostředkovává k němu přístup.
	/// <summary>
	/// EventInfo : obálka nad daty IEventItem. S tímto objektem se v datech pracuje, tento objekt v sobě hostuje IEventItem a zprostředkovává k němu přístup.
	/// </summary>
    public class EventInfo : BaseInfo, IDataElement
	{
		#region Konstrukce a property
		public EventInfo(ProcessInfo process, IEventItem eventItem)
			: base(process.Main, eventItem.EventKey)
		{
            this.Process = process;
            this.EventItem = eventItem;
        }
        public override void Dispose()
        {
            base.Dispose();

            this.Process = null;

            if (this.EventItem != null)
                this.EventItem.Dispose();

            this.EventItem = null;
        }
        public override string ToString()
        {
            return this.EventItem.ToString();
        }
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public override int ClassNr { get { return Constants.ClassNumberThing; } }
		/// <summary>
		/// Info o keywordu, do něhož tato událost patří
		/// </summary>
		public ProcessInfo Process { get; private set; }
		/// <summary>
		/// Info o aplikačním serveru, do něhož tato událost patří
		/// </summary>
		public ServerInfo Server { get { return this.Process.Server; } }
		/// <summary>
		/// Přímý přístup na objekt IEventItem, který reprezentuje událost. Objekt je vytvořen readerem.
		/// </summary>
		public IEventItem EventItem { get; private set; }
		/// <summary>
		/// Porovná dvě události z hlediska jejich počátečního času
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
        public static int CompareByBegin(EventInfo a, EventInfo b)
		{
			int cmp = a.Time.Begin.CompareTo(b.Time.Begin);
			if (cmp != 0) return cmp;

			// Řešení případů, kdy čas dvou událostí je identický: 
			// a) pokud nejsou položky ze stejného souboru, pak porovnám počátek souborů:
			//if (!Object.Equals(a.TraceBegin, b.TraceBegin))
			//    return TraceInfo.CompareByBegin(a.TraceBegin, b.TraceBegin);

			// b) pokud jsou položky ze stejného souboru, pak využiju číslo řádku (pořadové číslo řádku v souboru):
			return a.LineNumber.CompareTo(b.LineNumber);
		}
        #endregion
		#region Podpora pro mergování
		/// <summary>
		/// Příznak, že tato událost je stále chodná k dalšímu mergování
		/// </summary>
		public bool IsMergable
		{
			get { return (!this.MergeKey.IsEmpty() && this.MergedIntoEventId == 0); }
		}
		/// <summary>
		/// Příznak, že tato událost byla úspěšně mergována do jiné události, 
		/// </summary>
		public bool MergedOut { get { return (this.MergedIntoEventId != 0); } }
		/// <summary>
		/// Id události, do níž byla tato událost mergována = přidána.
		/// </summary>
		public int MergedIntoEventId { get; set; }
		#endregion
        #region Přímý přístup na členy this.EventItem, interface IEventItem, kromě IDataElement, statické komparátory
        /// <summary>
        /// Klíč procesu. Nemůže být prázdný (pak se event nikam nezařadí; ledaže bychom povolili jeden empty proces - ale zatím ne).
        /// </summary>
        public string ProcessKey { get { return this.EventItem.ProcessKey; } }
        /// <summary>
        /// Klíč události. Může být empty, pak ale nebude možno do této události přidávat další data o události (IEventEdge), 
        /// protože je nebude možno spárovat.
        /// </summary>
        public string EventKey { get { return this.EventItem.EventKey; } }
        /// <summary>
        /// Klíč skupiny událostí, které lze mezi sebou mergovat.
        /// Mergování je činnost, k níž dochází po dokončení načítání dat z trace souborů.
        /// Jejím účelem je najít formálně izolované události, které ale logicky patří do jedné události.
        /// Typickým představitelem je událost typu Cowley, která se může skládat z 5 samostatných událostí (HetCowley, RunPart1, RunPart3, RunPart4, RunPart5),
        /// ale pro uživatele má význam ji zobrazit najednou = a navíc dojde ke spojení různých fragmentů dat (číslo a název cowley, číslo třídy, název typu, metoda).
        /// Události, které se nechtějí mergovat, zde vrací null (nebo empty string).
        /// Události které mergovat chtějí, zde vrací název své skupiny, mergování pak probíhá jen pro události této skupiny.
        /// Pokračování v popisu metody IEventItem.MergeWith()
        /// </summary>
        public string MergeKey { get { return this.EventItem.MergeKey; } }
        /// <summary>
        /// Klíč domácího nodu, kam patří události tohoto procesu.
        /// Nemusí být přítomen v každé události, ale v rámci jednoho procesu nesmí být zadány dva různé údaje.
        /// </summary>
        public string TargetHomeNode { get { return this.EventItem.TargetHomeNode; } }
		/// <summary>
		/// Vrací true, pokud tento event potřebuje TargetHomeNode (protože v jeho cestách je alespoň jedna relativní)
		/// </summary>
		public bool TargetHomeNodeRequired
		{
			get
			{
				if (this.TreePaths != null)
					return (this.TreePaths.Any(p => !p.AbsolutePath));
				if (this.TargetPaths != null)
					return (this.TargetPaths.Any(p => !TreePath.IsAbsolute(p)));
				return false;
			}
		}
        /// <summary>
        /// Klíče (cesty) k cílovým nodům, kam se tato událost má zobrazit.
        /// </summary>
        public string[] TargetPaths { get { return this.EventItem.TargetPaths; } }
		/// <summary>
		/// Plné definice cest k nodům, kde se má tento element zobrazit.
		/// Pokud bude null, pak se načtou klíče z TargetPaths, a plnou definici musí určit TraceReader.
		/// </summary>
		public TreePath[] TreePaths { get { return this.EventItem.TreePaths; } }
		/// <summary>
        /// Čas události načtený z trace souboru
        /// </summary>
        public TimeRange Time { get { return this.EventItem.Time; } }
        /// <summary>
        /// Čas události vizuální = zde je akceptován časový posun
        /// </summary>
        public TimeRange TimeWithOffset { get { return this._GetEventTimeRange(); } }
        /// <summary>
        /// Vrací čas eventu buď z trace souboru, anebo eliminovaný o časový posun
        /// </summary>
        /// <param name="timeSelect"></param>
        /// <returns></returns>
        public TimeRange TimeBySelect(TraceEventTimeSelect timeSelect)
        {
            switch (timeSelect)
            {
                case TraceEventTimeSelect.TraceTime: return this.Time;
                case TraceEventTimeSelect.VisualTime: return this.TimeWithOffset;
            }
            return this.Time;
        }
        /// <summary>
        /// Pořadové číslo události v rámci trace souboru, 
        /// slouží k přesnému třídění pořadí událostí tehdy, když čas dvou sousedních událostí je zcela shodný.
        /// Ono za 1 milisekundu lze stihnout desítky věcí...
        /// </summary>
        public int LineNumber { get { return this.EventItem.LineNumber; } }
        /// <summary>
        /// Vloží do sebe další data z položky trace souboru.
        /// Po vytvoření tohoto objektu neobsahuje vlastní data, jen klíče.
        /// Vlastní data se vkládají opakovaným voláním této metody (přinejmenším 1x).
        /// Vkládaná data mají shodnou hodnotu EventKey, jak tento objekt.
        /// Podle toho byl tento objekt vytvořen a poté při další položce dohledán.
        /// </summary>
        /// <param name="eventEdge"></param>
        public void AddData(IEventEdge eventEdge, TraceInfo traceInfo)
        {
            this.EventItem.AddData(eventEdge, traceInfo);
        }
        /// <summary>
        /// Zkusí, zda tato událost (this) může být spojena s událostí časově následující ze stejné mergovací skupiny.
        /// Pokud ano, pak se v této metodě musí prolnout data obou událostí do this, a musí se vrátit true.
        /// Pokud je mergovat nelze, vrací se false.
        /// </summary>
        /// <param name="nextEventItem">Časově následující událost</param>
        /// <returns></returns>
        public bool MergeWith(EventInfo nextEvent)
        {
            bool merged = this.EventItem.MergeWith(nextEvent.EventItem);
            if (merged)
            {
                nextEvent.MergedIntoEventId = this.Id;
            }
            return merged;
        }
        /// <summary>
        /// Skupina barevnosti, umožní konfigurovat barvy pro jednotlivé skupiny událostí bez toho, aby tuto konfiguraci řešila datová vrstva.
        /// Ta jen nadefinuje logickou skupinu barvy, ale hodnotu určuje konfigurace.
        /// </summary>
        string ColorGroup { get { return this.EventItem.ColorGroup; } }
        /// <summary>
        /// Varianta barevnosti, používá se společně s this.ColorGroup.
        /// Elementy, které mají shodnou property ColorGroup i ColorIndex budou mít shodnou barvu.
        /// Hodnota ColorIndex dovoluje variovat základní barvu definovanou pro skupinu ColorGroup.
        /// Implicitní hodnota je přebírána z this.LineNumber, po vložení jiné hodnoty je vracena tato explicitní hodnota.
        /// Je vhodné vkládat například číslo funkce, číslo třídy instance atd, pak všechny výskyty této funkce mají stejnou barvu.
        /// Zohlední se pouze tehdy, když je vygenerován element s barvou Empty.
        /// </summary>
        int ColorIndex { get { return this.EventItem.ColorIndex; } }
		/// <summary>
		/// Setřídí události podle TimeRange.Begin ASC, SubTimeSort ASC, ElementGroup ASC, TimeRange.End DESC
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static int CompareByBeginSubGroupEnd(EventInfo a, EventInfo b)
		{
			return CompareByBeginSubGroupEnd(a.EventItem, b.EventItem);
		}
		/// <summary>
        /// Setřídí události podle TimeRange.Begin ASC, SubTimeSort ASC, ElementGroup ASC, TimeRange.End DESC
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByBeginSubGroupEnd(IEventItem a, IEventItem b)
        {
            TimeRange at = a.TimeRange;
            TimeRange bt = b.TimeRange;
            
            int cmp = at.Begin.CompareTo(bt.Begin);
            if (cmp != 0) return cmp;

            cmp = a.SubTimeSort.CompareTo(b.SubTimeSort);
            if (cmp != 0) return cmp;

            cmp = a.ElementGroup.CompareTo(b.ElementGroup);
            if (cmp != 0) return cmp;
            
            return bt.End.CompareTo(at.End);
        }
        #endregion
        #region Explicitní implementace interface IDataElement, z větší části průhledností do this.EventItem (pomocí interface IEventItem)
        bool IDataElement.ElementVisible
        {
            get 
            {
                if (this.MergedOut) return false;
                return this.EventItem.ElementVisible;
            }
        }
        GID IDataElement.GId { get { return this.GId; } }
        TimeRange IDataElement.TimeRange { get { return this.TimeWithOffset; } set { } }
        float IDataElement.ElementHeightRatio { get { return this.EventItem.ElementHeightRatio; } }
		bool IDataElement.BeginIsUncertain { get { return this.EventItem.BeginIsUncertain; } }
        bool IDataElement.EndIsUncertain { get { return this.EventItem.EndIsUncertain; } }
        int IDataElement.SubTimeSort { get { return this.EventItem.SubTimeSort; } }
        bool IDataElement.FullRow { get { return false; } }
        bool IDataElement.HideZeroWidthElementRectangle { get { return this.EventItem.HideZeroWidthElementRectangle; } }
        float[] IDataElement.Fragments { get { return this.EventItem.Fragments; } }
        GraphLayerType IDataElement.ElementLayer { get { return this.EventItem.ElementLayer; } }
        int IDataElement.ElementGroup { get { return this.EventItem.ElementGroup; } }
        GraphElementShapeType IDataElement.ElementShapeType { get { return this.EventItem.ElementShapeType; } }
        ElementProperties IDataElement.Properties { get { return this.EventItem.Properties; } }
        ElementActionType IDataElement.ActionOnDoubleClick { get { return this.EventItem.ActionOnDoubleClick; } }
        GraphElementHandler IDataElement.DoubleClick { get { return this.EventItem.DoubleClick; } }
		string IDataElement.ColorGroup { get { return this.ColorGroup; } }
        Color IDataElement.BackColor { get { return _GetColorBack(); } set { } }
        Color IDataElement.FontColor { get { return _GetColorFont(); } }
        bool IDataElement.CaptionShowAllways { get { return this.EventItem.CaptionShowAllways; } }
        Image IDataElement.ElementImage { get { return this.EventItem.ElementImage; } }
        ContentAlignment IDataElement.ImagePivotElementPoint { get { return this.EventItem.ImagePivotElementPoint; } }
        ContentAlignment IDataElement.ImagePivotImagePoint { get { return this.EventItem.ImagePivotImagePoint; } }
        string IDataElement.ToolTipInfo { get { return this.EventItem.ToolTipInfo; } }
        string[] IDataElement.Captions { get { return this.EventItem.Captions; } }

        string IDataElement.GetToolTipInfo(IDataRow rowData) { return this.EventItem.GetToolTipInfo(rowData); }
        string IDataElement.GetCaption(IDataRow rowData, float charCount) { return this.EventItem.GetCaption(rowData, charCount); }
        void IDataElement.DrawElement(GraphElementPainterArgs args) { this.EventItem.DrawElement(args); }
        /// <summary>
        /// Vrátí aktuální čas tohoto eventu.
        /// Zde se řeší problematika eventů, které mají mít Begin a End, ale jeden z nich dosud nebyl načten (druhý načten byl, jinak by nebyl event).
        /// Jak chybějící strana času se dočasně přebírá čas celého serveru.
        /// </summary>
        /// <returns></returns>
        private TimeRange _GetEventTimeRange()
        {
            TimeRange time = this.EventItem.TimeRange;
            if (this.EventItem.BeginIsUncertain || this.EventItem.EndIsUncertain)
            {   // Začátek nebo konec je nejistý
                TimeRange serverTime = this.Server.Time;
                if (this.EventItem.BeginIsUncertain)
                    time = new TimeRange(serverTime.Begin, time.End);
                if (this.EventItem.EndIsUncertain)
                    time = new TimeRange(time.Begin, serverTime.End);
            }
            // Časový offset = řešení situace, kdy různé servery mají různý systémový čas, ale přesto je chceme zobrazovat synchronně:
            return this.Server.GetVisualTime(time);
        }
        /// <summary>
        /// Vrátí zdejší časový interval zarovnaný do total timerange.
        /// Akceptuje nastavení this.OneDimensional.
        /// </summary>
        /// <param name="thisRange"></param>
        /// <param name="totalTimeRange"></param>
        /// <returns></returns>
        protected virtual TimeRange RoundTimeRange(TimeRange time, TimeRange totalTimeRange)
        {
            bool oneDimensional = (time.Begin > DateTime.MinValue && time.Begin == time.End);

            if (oneDimensional)
            {   // Jednorozměrný čas
                // Neřeším příslušnost do času, jen rovnost End = Begin:
                if (time.End == time.Begin)
                    return time;
                return new TimeRange(time.Begin, time.Begin);
            }
            else
            {   // Čas OD-DO:
                if (time.Begin >= totalTimeRange.Begin && time.End <= totalTimeRange.End) return time;
                if (time.Begin < totalTimeRange.Begin)
                    time.Begin = totalTimeRange.Begin;
                if (time.End < time.Begin || time.End > totalTimeRange.End)
                    time.End = totalTimeRange.End;
                return time;
            }
        }
        #endregion
        #region Tvorba barvy pozadí + popředí
        /// <summary>
        /// Vrátí barvu pozadí. 
        /// Prioritní je this.EventItem.BackColor, pokud tam je Empty, pak využívá this.ColorGroup ve spojení s this.Main.TraceColors.
        /// Získanou barvu ukládá do lokální cache this._BackColor.
        /// </summary>
        /// <returns></returns>
        private Color _GetColorBack()
        {
            Color backColor = (this._ColorListGeneration == this.Main.TraceColors.ColorListGeneration ? this._BackColor : Color.Empty);
            if (backColor.IsEmpty)
            {
                backColor = this.EventItem.BackColor;
                if (backColor.IsEmpty)
                    backColor = this.Main.GetColor(this.ColorGroup, this.Id);
                this._ColorListGeneration = this.Main.TraceColors.ColorListGeneration;
                this._BackColor = backColor;
            }
            return backColor;
        }
        /// <summary>
        /// Vrátí barvu písma.
        /// Prioritní je this.EventItem.FontColor, pokud tam je Empty, pak vrací barvu kontrastní k barvě this._GetColorBack()
        /// </summary>
        /// <returns></returns>
        private Color _GetColorFont()
        {
            Color fontColor = this.EventItem.FontColor;
            if (fontColor.IsEmpty)
                fontColor = _GetColorBack().Contrast();
            return fontColor;
        }
        /// <summary>
        /// Uložená hodnota barvy BackColor
        /// </summary>
        private Color _BackColor;
        /// <summary>
        /// Hodnota Main.TraceColors.ColorListGeneration, pro kterou byla určena barva this._BackColor.
        /// Po změně nastavení barev se změní i hodnota Main.TraceColors.ColorListGeneration, následně se znovu přepočítá hodnota this._BackColor.
        /// </summary>
        private int _ColorListGeneration;
        #endregion
    }
	#endregion
	#region BaseInfo : podkladová třída všech datových tříd (kromě obálky ItemInfo)
	public abstract class BaseInfo : IIdKey, IDisposable
	{
		public BaseInfo(MainInfo main, string key)
		{
			this.Main = main;
			this.Id = main.NextID(this);
			this.Key = FormatKey(key);
		}
		/// <summary>
		/// Pouze pro potomky, pro účely Pluginů
		/// </summary>
		protected BaseInfo() { }
        public virtual void Dispose()
        {
            this.Main = null;
        }
        public override string  ToString()
        {
 	         return this.GId.StringShort + ": " + this.Key.ToStringNull();
        }
		/// <summary>
		/// Reference na Main objekt. Každý datový objekt se k němu může dostat.
		/// </summary>
		public MainInfo Main { get; private set; }
        /// <summary>
        /// Reference na Trace dokument = projekt
        /// </summary>
        public TraceDocument Document { get { return (this.Main == null ? null : this.Main.Document); } }
        /// <summary>
        /// Reference na Auditlog Trace dokumentu
        /// </summary>
        public AuditLog DocumentAuditlog { get { return (this.Main == null ? null : (this.Main.Document == null ? null : this.Main.Document.Auditlog)); } }
        /// <summary>
		/// ID záznamu
		/// </summary>
		public int Id { get; private set; }
		/// <summary>
		/// Číslo třídy pro GID
		/// </summary>
		public abstract int ClassNr { get; }
		/// <summary>
		/// GID tohoto objektu
		/// </summary>
		public GID GId { get { return new GID(ClassNr, this.Id); } }
		/// <summary>
		/// Klíčový název záznamu. Může být null. Podle názvu lze hledat.
		/// </summary>
		public string Key { get; private set; }
		/// <summary>
		/// Data k obecnému použití
		/// </summary>
		public object Tag { get; set; }
		/// <summary>
		/// Formátuje klíč (vrací null, anebo key.Trim().ToLower();)
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public static string FormatKey(string key)
		{
			if (string.IsNullOrEmpty(key))
				return null;
			return key.Trim().ToLower();
		}
        /// <summary>
        /// Porovná dvě instance podle jejich Key. Pro třídění.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByKey(BaseInfo a, BaseInfo b)
        {
            return String.Compare(a.Key, b.Key, true);
        }
	}
	#endregion
    #region AuditLog : seznam položek Auditlogu. AuditItem : položka auditlogu s libovolnou řadou detailů
    /// <summary>
    ///  AuditLog : seznam položek Auditlogu
    /// </summary>
    public class AuditLog : List<TraceVisualiser.Forms.IMasterDetails>
    {
        public AuditItem AddAuditItem(string name, params Forms.MasterDetailItem[] details)
        {
            AuditItem item = new AuditItem(name, details);
            this.Add(item);
            return item;
        }
        /// <summary>
        /// Do auditlogu přidá další položku typu Chyba
        /// </summary>
        /// <param name="exc"></param>
        /// <param name="details"></param>
        /// <returns></returns>
        public AuditItem AddAuditError(Exception exc, params Forms.MasterDetailItem[] details)
        {
            AuditItem item = new AuditItem("Error", MasterDetailItem.LevelError);
            if (details != null)
                item.DetailList.AddRange(details);
            item.DetailList.Add(new MasterDetailItem("Message", exc.Message, new Size(250, 0)));
            item.DetailList.Add(new MasterDetailItem("Stack", exc.StackTrace, new Size(350, 63)));
            this.Add(item);
            return item;
        }
        /// <summary>
        /// Zobrazí auditlog jako nemodální (jedno z mnoha) okno
        /// </summary>
        /// <param name="titleColumn"></param>
        /// <param name="includeInfo"></param>
        public void Show(string titleColumn, bool includeInfo)
        {
            this._Show(titleColumn, includeInfo, false);
        }
        /// <summary>
        /// Zobrazí auditlog jako modální okno (navrchu, před pokračováním se musí zavřít)
        /// </summary>
        /// <param name="titleColumn"></param>
        /// <param name="includeInfo"></param>
        public void ShowModal(string titleColumn, bool includeInfo)
        {
            this._Show(titleColumn, includeInfo, true);
        }
        /// <summary>
        /// Příznak, že auditlog obsahuje nějaký Warning
        /// </summary>
        public bool ContainWarning
        {
            get
            {
                if (this._FindDetail(MasterDetailItem.LevelWarning) != null) return true;
                return false;
            }
        }
        /// <summary>
        /// Příznak, že auditlog obsahuje nějaký Error
        /// </summary>
        public bool ContainError
        {
            get
            {
                if (this._FindDetail(MasterDetailItem.LevelError) != null) return true;
                return false;
            }
        }
        /// <summary>
        /// Příznak, že auditlog obsahuje Warning nebo Error
        /// </summary>
        public bool ContainWarningOrError
        {
            get
            {
                if (this._FindDetail(MasterDetailItem.LevelWarning) != null) return true;
                if (this._FindDetail(MasterDetailItem.LevelError) != null) return true;
                return false;
            }
        }
        /// <summary>
        /// Zobrazí auditlog jako modální nebo nemodální okno
        /// </summary>
        /// <param name="titleColumn"></param>
        /// <param name="includeInfo"></param>
        private void _Show(string titleColumn, bool includeInfo, bool modal)
        {
            bool show = (this.Count > 0);
            if (show && !includeInfo)
            {
                // Pokud je počet položek větší než nula, ale nemají se zobrazovat pouhé informace, zjistím zda existuje Warning nebo Error:
                if (this._FindDetail(MasterDetailItem.LevelWarning) == null &&
                    this._FindDetail(MasterDetailItem.LevelError) == null)
                    show = false;
            }
            if (show)
            {
                // Žádný using, okno může být nemodální a pak by ho using zastřelil:
                TraceVisualiser.Forms.EventDetailForm edf = new Forms.EventDetailForm();

                edf.ViewMode = EventDetailViewMode.Grid;
                edf.TitleColumnText = titleColumn;
                edf.ShowDetailItems = this;

                if (modal)
                {
                    // Modální okno = navrchu, před pokračováním se musí zavřít:
                    edf.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
                    edf.MaximizeBox = false;
                    edf.MinimizeBox = false;
                    edf.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    edf.TopMost = true;
                    Steward.ExecuteInTryCatch(delegate { edf.ShowDialog(); }, "Při práci v okně došlo k chybě.");

                    edf.Dispose();
                }
                else
                {
                    // Nemodální okno = jedno z mnoha oken systému:
                    edf.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    Steward.MdiChildAddNew(edf);
                }
            }
        }
        /// <summary>
        /// Vrátí prvek auditlogu, který ve svých detailech obsahuje dané jméno a hodnotu.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private TraceVisualiser.Forms.IMasterDetails _FindDetail(MasterDetailItem detail)
        {
            return _FindDetail(detail.Name, detail.Value);
        }
        /// <summary>
        /// Vrátí prvek auditlogu, který ve svých detailech obsahuje dané jméno a hodnotu.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private TraceVisualiser.Forms.IMasterDetails _FindDetail(string name, object value)
        {
            foreach (TraceVisualiser.Forms.IMasterDetails md in this)
            {
                bool contain = md.Details.Any(d => d.Name == name && d.Value == value);
                if (contain) return md;
            }
            return null;
        }
    }
    /// <summary>
    /// AuditItem : položka auditlogu s libovolnou řadou detailů
    /// </summary>
    public class AuditItem : TraceVisualiser.Forms.IMasterDetails
    {
        /// <summary>
        /// Vytvoří položku auditlogu
        /// </summary>
        /// <param name="name"></param>
        /// <param name="details"></param>
        public AuditItem(string name, params Forms.MasterDetailItem[] details)
        {
            this.MasterName = name;
            this.DetailBackColor = Color.Empty;
            this.DetailList = new List<Forms.MasterDetailItem>(details);
        }
        /// <summary>
        /// Vytvoří položku auditlogu
        /// </summary>
        /// <param name="name"></param>
        /// <param name="details"></param>
        public AuditItem(string name, Color backColor, params Forms.MasterDetailItem[] details)
        {
            this.MasterName = name;
            this.DetailBackColor = backColor;
            this.DetailList = new List<Forms.MasterDetailItem>(details);
        }
        public List<Forms.MasterDetailItem> DetailList { get; set; }
        #region IMasterDetails Members
        public string MasterName { get; set; }
        public Color DetailBackColor { get; set; }
        public IEnumerable<Forms.MasterDetailItem> Details { get { return this.DetailList; } }
        #endregion
    }
    #endregion
    #region Constants
    internal class Constants
    {
        /// <summary>
        /// 0x0001: Číslo třídy dat: Main základna
        /// </summary>
        public const int ClassNumberMain = 0x0001;
        /// <summary>
        /// 0x0002: Číslo třídy dat: Aplikační server
        /// </summary>
        public const int ClassNumberServer = 0x0002;
        /// <summary>
        /// 0x0003: Číslo třídy dat: Trace soubor
        /// </summary>
        public const int ClassNumberTrace = 0x0003;
        /// <summary>
        /// 0x0004: Číslo třídy dat: Řádek trace souboru
        /// </summary>
        public const int ClassNumberLine = 0x0004;
        /// <summary>
        /// 0x0005: Číslo třídy dat: Událost trace souboru (sestavená ze dvou řádků Begin-End, anebo z jednoho řádku System)
        /// </summary>
        public const int ClassNumberThing = 0x0005;
        /// <summary>
        /// 0x0011: Číslo třídy dat: Process (například jedno přihlášení jednoho uživatele)
        /// </summary>
        public const int ClassNumberProcess = 0x0011;
		/// <summary>
		/// 0x0012: Číslo třídy dat: Merge (svazek událostí, které lze mergovat)
		/// </summary>
		public const int ClassNumberMerge = 0x0012;
		/// <summary>
        /// 0x0020: Číslo třídy dat: NrsInstance (události, které se týkají instance)
        /// </summary>
        public const int ClassNumberNrsInstance = 0x0020;
        /// <summary>
        /// 0x0021: Číslo třídy dat: NrsCowley (události, které se týkají funkce)
        /// </summary>
        public const int ClassNumberNrsCowley = 0x0021;
        /// <summary>
        /// 0x0030: Číslo třídy dat: SQL PID (proces na SQL serveru, vytvořený pro jednu Connection)
        /// </summary>
        public const int ClassNumberSqlId = 0x0030;
		/// <summary>
		/// 0x0100: Číslo třídy dat: Obecný node (uzel stromu)
		/// </summary>
		public const int ClassNumberNode = 0x0100;

        // zahodit
        /// <summary>
        /// 0x0010: Číslo třídy dat: Uživatel
        /// </summary>
        public const int ClassNumberUser = 0x0090;
        /// <summary>
        /// 0x0011: Číslo třídy dat: Session (jedno přihlášení jednoho uživatele)
        /// </summary>
        public const int ClassNumberConnect = 0x0091;
		/// <summary>
		/// 0x0012: Číslo třídy dat: Keyword (jedna skupina událostí v jedné session)
		/// </summary>
		public const int ClassNumberKeyword = 0x0092;


		public const string NodeMainRoot = "Root";
		public const string NodeMainTrace = "\\01:Trace";
	}
	#endregion
    #region Enums (MergeMode, DupliciteEventKeyHandlingType)
    /// <summary>
	/// Režim slučování prvků do jednoho výsledného
	/// </summary>
	public enum MergeMode
	{
		/// <summary>
		/// Tento prvek nelze slučovat s jinými prvky
		/// </summary>
		None = 0,
		/// <summary>
		/// Tento prvek lze slučovat s jinými prvky, ale dosud nebyl sloučen
		/// </summary>
		Mergable,
		/// <summary>
		/// Tento prvek je sloučen do jiného prvku
		/// </summary>
		MergedOut,
		/// <summary>
		/// Tento prvek je sloučen s dalšími prvky (a může další ještě prvky do sebe pojmout)
		/// </summary>
		MergedHere
	}
    /// <summary>
    /// Režim zacházení s duplicitou při přidávání EventInfo do seznamu eventů procesu.
    /// </summary>
    public enum DupliciteEventKeyHandlingType
    {
        /// <summary>
        /// Žádná zvláštní obsluha. Duplicity se neočekávají (typicky pro trace, které generují EventEdge, a podle jejich EventKey se dohledává EventInfo = standardní aplikační a klientský trace).
        /// </summary>
        None = 0,
        /// <summary>
        /// Před přidáním eventu se ověří, zda neexistuje. Chvilku to zdržuje. 
        /// Při existenci (duplicita) se vyhodí chyba a soubor se dál nezpracuje.
        /// </summary>
        ExplicitCheck,
        /// <summary>
        /// Před přidáním eventu se testuje, zda event neexistuje.
        /// Při existenci (duplicita) se zapíše Warning do auditlogu, ale pokračuje se dál bez chyby.
        /// </summary>
        AuditAndSkip,
        /// <summary>
        /// Před přidáním eventu se testuje, zda event neexistuje.
        /// Při existenci (duplicita) se event nezapisuje, ignoruje a ani se nezapíše Warning do auditlogu. Pokračuje se dál bez chyby.
        /// </summary>
        SilentSkip
    }
    /// <summary>
    /// Určuje druh času události: buď z trace souboru (jak je tam uvedeno beze změn), anebo eliminovaný o časový posun
    /// </summary>
    public enum TraceEventTimeSelect
    {
        TraceTime,
        VisualTime
    }
	#endregion
}
